diff --git a/Amalgam/Amalgam.vcxproj b/Amalgam/Amalgam.vcxproj index 52c9b6f2..ebec96b9 100644 --- a/Amalgam/Amalgam.vcxproj +++ b/Amalgam/Amalgam.vcxproj @@ -827,6 +827,7 @@ + @@ -912,6 +913,7 @@ + diff --git a/Amalgam/Amalgam.vcxproj.filters b/Amalgam/Amalgam.vcxproj.filters index 87c5b794..deb09317 100644 --- a/Amalgam/Amalgam.vcxproj.filters +++ b/Amalgam/Amalgam.vcxproj.filters @@ -189,6 +189,8 @@ + + diff --git a/Amalgam/include/ImGui/LanguageDefinitions.cpp b/Amalgam/include/ImGui/LanguageDefinitions.cpp new file mode 100644 index 00000000..581d6b8d --- /dev/null +++ b/Amalgam/include/ImGui/LanguageDefinitions.cpp @@ -0,0 +1,937 @@ +#include "TextEditor.h" + +static bool TokenizeCStyleString(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) +{ + const char* p = in_begin; + + if (*p == '"') + { + p++; + + while (p < in_end) + { + // handle end of string + if (*p == '"') + { + out_begin = in_begin; + out_end = p + 1; + return true; + } + + // handle escape character for " + if (*p == '\\' && p + 1 < in_end && p[1] == '"') + p++; + + p++; + } + } + + return false; +} + +static bool TokenizeCStyleCharacterLiteral(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) +{ + const char* p = in_begin; + + if (*p == '\'') + { + p++; + + // handle escape characters + if (p < in_end && *p == '\\') + p++; + + if (p < in_end) + p++; + + // handle end of character literal + if (p < in_end && *p == '\'') + { + out_begin = in_begin; + out_end = p + 1; + return true; + } + } + + return false; +} + +static bool TokenizeCStyleIdentifier(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) +{ + const char* p = in_begin; + + if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_') + { + p++; + + while ((p < in_end) && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == '_')) + p++; + + out_begin = in_begin; + out_end = p; + return true; + } + + return false; +} + +static bool TokenizeCStyleNumber(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) +{ + const char* p = in_begin; + + const bool startsWithNumber = *p >= '0' && *p <= '9'; + + if (*p != '+' && *p != '-' && !startsWithNumber) + return false; + + p++; + + bool hasNumber = startsWithNumber; + + while (p < in_end && (*p >= '0' && *p <= '9')) + { + hasNumber = true; + + p++; + } + + if (hasNumber == false) + return false; + + bool isFloat = false; + bool isHex = false; + bool isBinary = false; + + if (p < in_end) + { + if (*p == '.') + { + isFloat = true; + + p++; + + while (p < in_end && (*p >= '0' && *p <= '9')) + p++; + } + else if (*p == 'x' || *p == 'X') + { + // hex formatted integer of the type 0xef80 + + isHex = true; + + p++; + + while (p < in_end && ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F'))) + p++; + } + else if (*p == 'b' || *p == 'B') + { + // binary formatted integer of the type 0b01011101 + + isBinary = true; + + p++; + + while (p < in_end && (*p >= '0' && *p <= '1')) + p++; + } + } + + if (isHex == false && isBinary == false) + { + // floating point exponent + if (p < in_end && (*p == 'e' || *p == 'E')) + { + isFloat = true; + + p++; + + if (p < in_end && (*p == '+' || *p == '-')) + p++; + + bool hasDigits = false; + + while (p < in_end && (*p >= '0' && *p <= '9')) + { + hasDigits = true; + + p++; + } + + if (hasDigits == false) + return false; + } + + // single precision floating point type + if (p < in_end && *p == 'f') + p++; + } + + if (isFloat == false) + { + // integer size type + while (p < in_end && (*p == 'u' || *p == 'U' || *p == 'l' || *p == 'L')) + p++; + } + + out_begin = in_begin; + out_end = p; + return true; +} + +static bool TokenizeCStylePunctuation(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) +{ + (void)in_end; + + switch (*in_begin) + { + case '[': + case ']': + case '{': + case '}': + case '!': + case '%': + case '^': + case '&': + case '*': + case '(': + case ')': + case '-': + case '+': + case '=': + case '~': + case '|': + case '<': + case '>': + case '?': + case ':': + case '/': + case ';': + case ',': + case '.': + out_begin = in_begin; + out_end = in_begin + 1; + return true; + } + + return false; +} + +static bool TokenizeLuaStyleString(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) +{ + const char* p = in_begin; + + bool is_single_quote = false; + bool is_double_quotes = false; + bool is_double_square_brackets = false; + + switch (*p) + { + case '\'': + is_single_quote = true; + break; + case '"': + is_double_quotes = true; + break; + case '[': + p++; + if (p < in_end && *(p) == '[') + is_double_square_brackets = true; + break; + } + + if (is_single_quote || is_double_quotes || is_double_square_brackets) + { + p++; + + while (p < in_end) + { + // handle end of string + if ((is_single_quote && *p == '\'') || (is_double_quotes && *p == '"') || (is_double_square_brackets && *p == ']' && p + 1 < in_end && *(p + 1) == ']')) + { + out_begin = in_begin; + + if (is_double_square_brackets) + out_end = p + 2; + else + out_end = p + 1; + + return true; + } + + // handle escape character for " + if (*p == '\\' && p + 1 < in_end && (is_single_quote || is_double_quotes)) + p++; + + p++; + } + } + + return false; +} + +static bool TokenizeLuaStyleIdentifier(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) +{ + const char* p = in_begin; + + if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_') + { + p++; + + while ((p < in_end) && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == '_')) + p++; + + out_begin = in_begin; + out_end = p; + return true; + } + + return false; +} + +static bool TokenizeLuaStyleNumber(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) +{ + const char* p = in_begin; + + const bool startsWithNumber = *p >= '0' && *p <= '9'; + + if (*p != '+' && *p != '-' && !startsWithNumber) + return false; + + p++; + + bool hasNumber = startsWithNumber; + + while (p < in_end && (*p >= '0' && *p <= '9')) + { + hasNumber = true; + + p++; + } + + if (hasNumber == false) + return false; + + if (p < in_end) + { + if (*p == '.') + { + p++; + + while (p < in_end && (*p >= '0' && *p <= '9')) + p++; + } + + // floating point exponent + if (p < in_end && (*p == 'e' || *p == 'E')) + { + p++; + + if (p < in_end && (*p == '+' || *p == '-')) + p++; + + bool hasDigits = false; + + while (p < in_end && (*p >= '0' && *p <= '9')) + { + hasDigits = true; + + p++; + } + + if (hasDigits == false) + return false; + } + } + + out_begin = in_begin; + out_end = p; + return true; +} + +static bool TokenizeLuaStylePunctuation(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) +{ + (void)in_end; + + switch (*in_begin) + { + case '[': + case ']': + case '{': + case '}': + case '!': + case '%': + case '#': + case '^': + case '&': + case '*': + case '(': + case ')': + case '-': + case '+': + case '=': + case '~': + case '|': + case '<': + case '>': + case '?': + case ':': + case '/': + case ';': + case ',': + case '.': + out_begin = in_begin; + out_end = in_begin + 1; + return true; + } + + return false; +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Cpp() +{ + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) + { + static const char* const cppKeywords[] = { + "alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit", "atomic_noexcept", "auto", "bitand", "bitor", "bool", "break", "case", "catch", "char", "char16_t", "char32_t", "class", + "compl", "concept", "const", "constexpr", "const_cast", "continue", "decltype", "default", "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float", + "for", "friend", "goto", "if", "import", "inline", "int", "long", "module", "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private", "protected", "public", + "register", "reinterpret_cast", "requires", "return", "short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "synchronized", "template", "this", "thread_local", + "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq" + }; + for (auto& k : cppKeywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", + "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "printf", "sprintf", "snprintf", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper", + "std", "string", "vector", "map", "unordered_map", "set", "unordered_set", "min", "max" + }; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + langDef.mTokenize = [](const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end, PaletteIndex& paletteIndex) -> bool + { + paletteIndex = PaletteIndex::Max; + + while (in_begin < in_end && isascii(*in_begin) && isblank(*in_begin)) + in_begin++; + + if (in_begin == in_end) + { + out_begin = in_end; + out_end = in_end; + paletteIndex = PaletteIndex::Default; + } + else if (TokenizeCStyleString(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::String; + else if (TokenizeCStyleCharacterLiteral(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::CharLiteral; + else if (TokenizeCStyleIdentifier(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Identifier; + else if (TokenizeCStyleNumber(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Number; + else if (TokenizeCStylePunctuation(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Punctuation; + + return paletteIndex != PaletteIndex::Max; + }; + + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; + + langDef.mCaseSensitive = true; + + langDef.mName = "C++"; + + inited = true; + } + return langDef; +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Hlsl() +{ + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) + { + static const char* const keywords[] = { + "AppendStructuredBuffer", "asm", "asm_fragment", "BlendState", "bool", "break", "Buffer", "ByteAddressBuffer", "case", "cbuffer", "centroid", "class", "column_major", "compile", "compile_fragment", + "CompileShader", "const", "continue", "ComputeShader", "ConsumeStructuredBuffer", "default", "DepthStencilState", "DepthStencilView", "discard", "do", "double", "DomainShader", "dword", "else", + "export", "extern", "false", "float", "for", "fxgroup", "GeometryShader", "groupshared", "half", "Hullshader", "if", "in", "inline", "inout", "InputPatch", "int", "interface", "line", "lineadj", + "linear", "LineStream", "matrix", "min16float", "min10float", "min16int", "min12int", "min16uint", "namespace", "nointerpolation", "noperspective", "NULL", "out", "OutputPatch", "packoffset", + "pass", "pixelfragment", "PixelShader", "point", "PointStream", "precise", "RasterizerState", "RenderTargetView", "return", "register", "row_major", "RWBuffer", "RWByteAddressBuffer", "RWStructuredBuffer", + "RWTexture1D", "RWTexture1DArray", "RWTexture2D", "RWTexture2DArray", "RWTexture3D", "sample", "sampler", "SamplerState", "SamplerComparisonState", "shared", "snorm", "stateblock", "stateblock_state", + "static", "string", "struct", "switch", "StructuredBuffer", "tbuffer", "technique", "technique10", "technique11", "texture", "Texture1D", "Texture1DArray", "Texture2D", "Texture2DArray", "Texture2DMS", + "Texture2DMSArray", "Texture3D", "TextureCube", "TextureCubeArray", "true", "typedef", "triangle", "triangleadj", "TriangleStream", "uint", "uniform", "unorm", "unsigned", "vector", "vertexfragment", + "VertexShader", "void", "volatile", "while", + "bool1","bool2","bool3","bool4","double1","double2","double3","double4", "float1", "float2", "float3", "float4", "int1", "int2", "int3", "int4", "in", "out", "inout", + "uint1", "uint2", "uint3", "uint4", "dword1", "dword2", "dword3", "dword4", "half1", "half2", "half3", "half4", + "float1x1","float2x1","float3x1","float4x1","float1x2","float2x2","float3x2","float4x2", + "float1x3","float2x3","float3x3","float4x3","float1x4","float2x4","float3x4","float4x4", + "half1x1","half2x1","half3x1","half4x1","half1x2","half2x2","half3x2","half4x2", + "half1x3","half2x3","half3x3","half4x3","half1x4","half2x4","half3x4","half4x4", + }; + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "abort", "abs", "acos", "all", "AllMemoryBarrier", "AllMemoryBarrierWithGroupSync", "any", "asdouble", "asfloat", "asin", "asint", "asint", "asuint", + "asuint", "atan", "atan2", "ceil", "CheckAccessFullyMapped", "clamp", "clip", "cos", "cosh", "countbits", "cross", "D3DCOLORtoUBYTE4", "ddx", + "ddx_coarse", "ddx_fine", "ddy", "ddy_coarse", "ddy_fine", "degrees", "determinant", "DeviceMemoryBarrier", "DeviceMemoryBarrierWithGroupSync", + "distance", "dot", "dst", "errorf", "EvaluateAttributeAtCentroid", "EvaluateAttributeAtSample", "EvaluateAttributeSnapped", "exp", "exp2", + "f16tof32", "f32tof16", "faceforward", "firstbithigh", "firstbitlow", "floor", "fma", "fmod", "frac", "frexp", "fwidth", "GetRenderTargetSampleCount", + "GetRenderTargetSamplePosition", "GroupMemoryBarrier", "GroupMemoryBarrierWithGroupSync", "InterlockedAdd", "InterlockedAnd", "InterlockedCompareExchange", + "InterlockedCompareStore", "InterlockedExchange", "InterlockedMax", "InterlockedMin", "InterlockedOr", "InterlockedXor", "isfinite", "isinf", "isnan", + "ldexp", "length", "lerp", "lit", "log", "log10", "log2", "mad", "max", "min", "modf", "msad4", "mul", "noise", "normalize", "pow", "printf", + "Process2DQuadTessFactorsAvg", "Process2DQuadTessFactorsMax", "Process2DQuadTessFactorsMin", "ProcessIsolineTessFactors", "ProcessQuadTessFactorsAvg", + "ProcessQuadTessFactorsMax", "ProcessQuadTessFactorsMin", "ProcessTriTessFactorsAvg", "ProcessTriTessFactorsMax", "ProcessTriTessFactorsMin", + "radians", "rcp", "reflect", "refract", "reversebits", "round", "rsqrt", "saturate", "sign", "sin", "sincos", "sinh", "smoothstep", "sqrt", "step", + "tan", "tanh", "tex1D", "tex1D", "tex1Dbias", "tex1Dgrad", "tex1Dlod", "tex1Dproj", "tex2D", "tex2D", "tex2Dbias", "tex2Dgrad", "tex2Dlod", "tex2Dproj", + "tex3D", "tex3D", "tex3Dbias", "tex3Dgrad", "tex3Dlod", "tex3Dproj", "texCUBE", "texCUBE", "texCUBEbias", "texCUBEgrad", "texCUBElod", "texCUBEproj", "transpose", "trunc" + }; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([ \t]*#[ \t]*[a-zA-Z_]+)##", PaletteIndex::Preprocessor)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(L?\"(\\.|[^\"])*\")##", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(\'\\?[^\']\')##", PaletteIndex::CharLiteral)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?[0-9]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[0-7]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([a-zA-Z_][a-zA-Z0-9_]*)##", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([\[\]\{\}\!\%\^\&\*\(\)\-\+\=\~\|\<\>\?\/\;\,\.])##", PaletteIndex::Punctuation)); + + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; + + langDef.mCaseSensitive = true; + + langDef.mName = "HLSL"; + + inited = true; + } + return langDef; +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Glsl() +{ + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) + { + static const char* const keywords[] = { + "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short", + "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", + "_Noreturn", "_Static_assert", "_Thread_local" + }; + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", + "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper" + }; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([ \t]*#[ \t]*[a-zA-Z_]+)##", PaletteIndex::Preprocessor)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(L?\"(\\.|[^\"])*\")##", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(\'\\?[^\']\')##", PaletteIndex::CharLiteral)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?[0-9]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[0-7]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([a-zA-Z_][a-zA-Z0-9_]*)##", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([\[\]\{\}\!\%\^\&\*\(\)\-\+\=\~\|\<\>\?\/\;\,\.])##", PaletteIndex::Punctuation)); + + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; + + langDef.mCaseSensitive = true; + + langDef.mName = "GLSL"; + + inited = true; + } + return langDef; +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Python() +{ + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) + { + static const char* const keywords[] = { + "False", "await", "else", "import", "pass", "None", "break", "except", "in", "raise", "True", "class", "finally", "is", "return", "and", "continue", "for", "lambda", "try", "as", "def", "from", "nonlocal", "while", "assert", "del", "global", "not", "with", "async", "elif", "if", "or", "yield" + }; + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "abs", "aiter", "all", "any", "anext", "ascii", "bin", "bool", "breakpoint", "bytearray", "bytes", "callable", "chr", "classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod", "enumerate", "eval", "exec", "filter", "float", "format", "frozenset", "getattr", "globals", "hasattr", "hash", "help", "hex", "id", "input", "int", "isinstance", "issubclass", "iter", "len", "list", "locals", "map", "max", "memoryview", "min", "next", "object", "oct", "open", "ord", "pow", "print", "property", "range", "repr", "reversed", "round", "set", "setattr", "slice", "sorted", "staticmethod", "str", "sum", "super", "tuple", "type", "vars", "zip", "__import__" + }; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##((b|u|f|r)?\"(\\.|[^\"])*\")##", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##((b|u|f|r)?'(\\.|[^'])*')##", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?[0-9]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[0-7]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([a-zA-Z_][a-zA-Z0-9_]*)##", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([\[\]\{\}\!\%\^\&\*\(\)\-\+\=\~\|\<\>\?\/\;\,\.\:])##", PaletteIndex::Punctuation)); + + langDef.mCommentStart = "\"\"\""; + langDef.mCommentEnd = "\"\"\""; + langDef.mSingleLineComment = "#"; + + langDef.mCaseSensitive = true; + + langDef.mName = "Python"; + + inited = true; + } + return langDef; +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::C() +{ + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) + { + static const char* const keywords[] = { + "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short", + "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", + "_Noreturn", "_Static_assert", "_Thread_local" + }; + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", + "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper" + }; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + langDef.mTokenize = [](const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end, PaletteIndex& paletteIndex) -> bool + { + paletteIndex = PaletteIndex::Max; + + while (in_begin < in_end && isascii(*in_begin) && isblank(*in_begin)) + in_begin++; + + if (in_begin == in_end) + { + out_begin = in_end; + out_end = in_end; + paletteIndex = PaletteIndex::Default; + } + else if (TokenizeCStyleString(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::String; + else if (TokenizeCStyleCharacterLiteral(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::CharLiteral; + else if (TokenizeCStyleIdentifier(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Identifier; + else if (TokenizeCStyleNumber(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Number; + else if (TokenizeCStylePunctuation(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Punctuation; + + return paletteIndex != PaletteIndex::Max; + }; + + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; + + langDef.mCaseSensitive = true; + + langDef.mName = "C"; + + inited = true; + } + return langDef; +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Sql() +{ + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) + { + static const char* const keywords[] = { + "ADD", "EXCEPT", "PERCENT", "ALL", "EXEC", "PLAN", "ALTER", "EXECUTE", "PRECISION", "AND", "EXISTS", "PRIMARY", "ANY", "EXIT", "PRINT", "AS", "FETCH", "PROC", "ASC", "FILE", "PROCEDURE", + "AUTHORIZATION", "FILLFACTOR", "PUBLIC", "BACKUP", "FOR", "RAISERROR", "BEGIN", "FOREIGN", "READ", "BETWEEN", "FREETEXT", "READTEXT", "BREAK", "FREETEXTTABLE", "RECONFIGURE", + "BROWSE", "FROM", "REFERENCES", "BULK", "FULL", "REPLICATION", "BY", "FUNCTION", "RESTORE", "CASCADE", "GOTO", "RESTRICT", "CASE", "GRANT", "RETURN", "CHECK", "GROUP", "REVOKE", + "CHECKPOINT", "HAVING", "RIGHT", "CLOSE", "HOLDLOCK", "ROLLBACK", "CLUSTERED", "IDENTITY", "ROWCOUNT", "COALESCE", "IDENTITY_INSERT", "ROWGUIDCOL", "COLLATE", "IDENTITYCOL", "RULE", + "COLUMN", "IF", "SAVE", "COMMIT", "IN", "SCHEMA", "COMPUTE", "INDEX", "SELECT", "CONSTRAINT", "INNER", "SESSION_USER", "CONTAINS", "INSERT", "SET", "CONTAINSTABLE", "INTERSECT", "SETUSER", + "CONTINUE", "INTO", "SHUTDOWN", "CONVERT", "IS", "SOME", "CREATE", "JOIN", "STATISTICS", "CROSS", "KEY", "SYSTEM_USER", "CURRENT", "KILL", "TABLE", "CURRENT_DATE", "LEFT", "TEXTSIZE", + "CURRENT_TIME", "LIKE", "THEN", "CURRENT_TIMESTAMP", "LINENO", "TO", "CURRENT_USER", "LOAD", "TOP", "CURSOR", "NATIONAL", "TRAN", "DATABASE", "NOCHECK", "TRANSACTION", + "DBCC", "NONCLUSTERED", "TRIGGER", "DEALLOCATE", "NOT", "TRUNCATE", "DECLARE", "NULL", "TSEQUAL", "DEFAULT", "NULLIF", "UNION", "DELETE", "OF", "UNIQUE", "DENY", "OFF", "UPDATE", + "DESC", "OFFSETS", "UPDATETEXT", "DISK", "ON", "USE", "DISTINCT", "OPEN", "USER", "DISTRIBUTED", "OPENDATASOURCE", "VALUES", "DOUBLE", "OPENQUERY", "VARYING","DROP", "OPENROWSET", "VIEW", + "DUMMY", "OPENXML", "WAITFOR", "DUMP", "OPTION", "WHEN", "ELSE", "OR", "WHERE", "END", "ORDER", "WHILE", "ERRLVL", "OUTER", "WITH", "ESCAPE", "OVER", "WRITETEXT" + }; + + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "ABS", "ACOS", "ADD_MONTHS", "ASCII", "ASCIISTR", "ASIN", "ATAN", "ATAN2", "AVG", "BFILENAME", "BIN_TO_NUM", "BITAND", "CARDINALITY", "CASE", "CAST", "CEIL", + "CHARTOROWID", "CHR", "COALESCE", "COMPOSE", "CONCAT", "CONVERT", "CORR", "COS", "COSH", "COUNT", "COVAR_POP", "COVAR_SAMP", "CUME_DIST", "CURRENT_DATE", + "CURRENT_TIMESTAMP", "DBTIMEZONE", "DECODE", "DECOMPOSE", "DENSE_RANK", "DUMP", "EMPTY_BLOB", "EMPTY_CLOB", "EXP", "EXTRACT", "FIRST_VALUE", "FLOOR", "FROM_TZ", "GREATEST", + "GROUP_ID", "HEXTORAW", "INITCAP", "INSTR", "INSTR2", "INSTR4", "INSTRB", "INSTRC", "LAG", "LAST_DAY", "LAST_VALUE", "LEAD", "LEAST", "LENGTH", "LENGTH2", "LENGTH4", + "LENGTHB", "LENGTHC", "LISTAGG", "LN", "LNNVL", "LOCALTIMESTAMP", "LOG", "LOWER", "LPAD", "LTRIM", "MAX", "MEDIAN", "MIN", "MOD", "MONTHS_BETWEEN", "NANVL", "NCHR", + "NEW_TIME", "NEXT_DAY", "NTH_VALUE", "NULLIF", "NUMTODSINTERVAL", "NUMTOYMINTERVAL", "NVL", "NVL2", "POWER", "RANK", "RAWTOHEX", "REGEXP_COUNT", "REGEXP_INSTR", + "REGEXP_REPLACE", "REGEXP_SUBSTR", "REMAINDER", "REPLACE", "ROUND", "ROWNUM", "RPAD", "RTRIM", "SESSIONTIMEZONE", "SIGN", "SIN", "SINH", + "SOUNDEX", "SQRT", "STDDEV", "SUBSTR", "SUM", "SYS_CONTEXT", "SYSDATE", "SYSTIMESTAMP", "TAN", "TANH", "TO_CHAR", "TO_CLOB", "TO_DATE", "TO_DSINTERVAL", "TO_LOB", + "TO_MULTI_BYTE", "TO_NCLOB", "TO_NUMBER", "TO_SINGLE_BYTE", "TO_TIMESTAMP", "TO_TIMESTAMP_TZ", "TO_YMINTERVAL", "TRANSLATE", "TRIM", "TRUNC", "TZ_OFFSET", "UID", "UPPER", + "USER", "USERENV", "VAR_POP", "VAR_SAMP", "VARIANCE", "VSIZE" + }; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(L?\"(\\.|[^\"])*\")##", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(\'[^\']*\')##", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?[0-9]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[0-7]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([a-zA-Z_][a-zA-Z0-9_]*)##", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([\[\]\{\}\!\%\^\&\*\(\)\-\+\=\~\|\<\>\?\/\;\,\.])##", PaletteIndex::Punctuation)); + + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "--"; + + langDef.mCaseSensitive = false; + + langDef.mName = "SQL"; + + inited = true; + } + return langDef; +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::AngelScript() +{ + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) + { + static const char* const keywords[] = { + "and", "abstract", "auto", "bool", "break", "case", "cast", "class", "const", "continue", "default", "do", "double", "else", "enum", "false", "final", "float", "for", + "from", "funcdef", "function", "get", "if", "import", "in", "inout", "int", "interface", "int8", "int16", "int32", "int64", "is", "mixin", "namespace", "not", + "null", "or", "out", "override", "private", "protected", "return", "set", "shared", "super", "switch", "this ", "true", "typedef", "uint", "uint8", "uint16", "uint32", + "uint64", "void", "while", "xor" + }; + + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "cos", "sin", "tab", "acos", "asin", "atan", "atan2", "cosh", "sinh", "tanh", "log", "log10", "pow", "sqrt", "abs", "ceil", "floor", "fraction", "closeTo", "fpFromIEEE", "fpToIEEE", + "complex", "opEquals", "opAddAssign", "opSubAssign", "opMulAssign", "opDivAssign", "opAdd", "opSub", "opMul", "opDiv" + }; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(L?\"(\\.|[^\"])*\")##", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(\'\\?[^\']\')##", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?[0-9]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[0-7]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([a-zA-Z_][a-zA-Z0-9_]*)##", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([\[\]\{\}\!\%\^\&\*\(\)\-\+\=\~\|\<\>\?\/\;\,\.])##", PaletteIndex::Punctuation)); + + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; + + langDef.mCaseSensitive = true; + + langDef.mName = "AngelScript"; + + inited = true; + } + return langDef; +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Lua() +{ + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) + { + static const char* const keywords[] = { + "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "goto", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while" + }; + + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "assert", "collectgarbage", "dofile", "error", "getmetatable", "ipairs", "loadfile", "load", "loadstring", "next", "pairs", "pcall", "print", "rawequal", "rawlen", "rawget", "rawset", + "select", "setmetatable", "tonumber", "tostring", "type", "xpcall", "_G", "_VERSION","arshift", "band", "bnot", "bor", "bxor", "btest", "extract", "lrotate", "lshift", "replace", + "rrotate", "rshift", "create", "resume", "running", "status", "wrap", "yield", "isyieldable", "debug","getuservalue", "gethook", "getinfo", "getlocal", "getregistry", "getmetatable", + "getupvalue", "upvaluejoin", "upvalueid", "setuservalue", "sethook", "setlocal", "setmetatable", "setupvalue", "traceback", "close", "flush", "input", "lines", "open", "output", "popen", + "read", "tmpfile", "type", "write", "close", "flush", "lines", "read", "seek", "setvbuf", "write", "__gc", "__tostring", "abs", "acos", "asin", "atan", "ceil", "cos", "deg", "exp", "tointeger", + "floor", "fmod", "ult", "log", "max", "min", "modf", "rad", "random", "randomseed", "sin", "sqrt", "string", "tan", "type", "atan2", "cosh", "sinh", "tanh", + "pow", "frexp", "ldexp", "log10", "pi", "huge", "maxinteger", "mininteger", "loadlib", "searchpath", "seeall", "preload", "cpath", "path", "searchers", "loaded", "module", "require", "clock", + "date", "difftime", "execute", "exit", "getenv", "remove", "rename", "setlocale", "time", "tmpname", "byte", "char", "dump", "find", "format", "gmatch", "gsub", "len", "lower", "match", "rep", + "reverse", "sub", "upper", "pack", "packsize", "unpack", "concat", "maxn", "insert", "pack", "unpack", "remove", "move", "sort", "offset", "codepoint", "char", "len", "codes", "charpattern", + "coroutine", "table", "io", "os", "string", "utf8", "bit32", "math", "debug", "package" + }; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + langDef.mTokenize = [](const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end, PaletteIndex& paletteIndex) -> bool + { + paletteIndex = PaletteIndex::Max; + + while (in_begin < in_end && isascii(*in_begin) && isblank(*in_begin)) + in_begin++; + + if (in_begin == in_end) + { + out_begin = in_end; + out_end = in_end; + paletteIndex = PaletteIndex::Default; + } + else if (TokenizeLuaStyleString(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::String; + else if (TokenizeLuaStyleIdentifier(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Identifier; + else if (TokenizeLuaStyleNumber(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Number; + else if (TokenizeLuaStylePunctuation(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Punctuation; + + return paletteIndex != PaletteIndex::Max; + }; + + langDef.mCommentStart = "--[["; + langDef.mCommentEnd = "]]"; + langDef.mSingleLineComment = "--"; + + langDef.mCaseSensitive = true; + + langDef.mName = "Lua"; + + inited = true; + } + return langDef; +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Cs() +{ + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) + { + static const char* const keywords[] = { + "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "in (generic modifier)", "int", "interface", "internal", "is", "lock", "long", "namespace", "new", "null", "object", "operator", "out", "out (generic modifier)", "override", "params", "private", "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "using static", "void", "volatile", "while" + }; + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "add", "alias", "ascending", "async", "await", "descending", "dynamic", "from", "get", "global", "group", "into", "join", "let", "orderby", "partial", "remove", "select", "set", "value", "var", "when", "where", "yield" + }; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(($|@)?\"(\\.|[^\"])*\")##", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?[0-9]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[0-7]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([a-zA-Z_][a-zA-Z0-9_]*)##", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([\[\]\{\}\!\%\^\&\*\(\)\-\+\=\~\|\<\>\?\/\;\,\.])##", PaletteIndex::Punctuation)); + + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; + + langDef.mCaseSensitive = true; + + langDef.mName = "C#"; + + inited = true; + } + return langDef; +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Json() +{ + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) + { + langDef.mKeywords.clear(); + langDef.mIdentifiers.clear(); + + + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(\"(\\.|[^\"])*\")##", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?)##", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([\[\]\{\}\!\%\^\&\*\(\)\-\+\=\~\|\<\>\?\/\;\,\.\:])##", PaletteIndex::Punctuation)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(false|true)##", PaletteIndex::Keyword)); + + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; + + langDef.mCaseSensitive = true; + + langDef.mName = "Json"; + + inited = true; + } + return langDef; +} \ No newline at end of file diff --git a/Amalgam/include/ImGui/TextEditor.cpp b/Amalgam/include/ImGui/TextEditor.cpp index def05927..6d62b8e7 100644 --- a/Amalgam/include/ImGui/TextEditor.cpp +++ b/Amalgam/include/ImGui/TextEditor.cpp @@ -1,56 +1,30 @@ #include -#include #include -#include -#include +#include +#include #include "TextEditor.h" +#define IMGUI_SCROLLBAR_WIDTH 14.0f +#define POS_TO_COORDS_COLUMN_OFFSET 0.33f #define IMGUI_DEFINE_MATH_OPERATORS #include "imgui.h" // for imGui::GetCurrentWindow() -// TODO -// - multiline comments vs single-line: latter is blocking start of a ML +#pragma warning( disable : 4267 ) +#pragma warning( disable : 4244 ) -template -bool equals(InputIt1 first1, InputIt1 last1, - InputIt2 first2, InputIt2 last2, BinaryPredicate p) -{ - for (; first1 != last1 && first2 != last2; ++first1, ++first2) - { - if (!p(*first1, *first2)) - return false; - } - return first1 == last1 && first2 == last2; -} +struct TextEditor::RegexList { + std::vector> mValue; +}; + + +// --------------------------------------- // +// ------------- Exposed API ------------- // TextEditor::TextEditor() - : mLineSpacing(1.0f) - , mUndoIndex(0) - , mTabSize(4) - , mOverwrite(false) - , mReadOnly(false) - , mWithinRender(false) - , mScrollToCursor(false) - , mScrollToTop(false) - , mTextChanged(false) - , mColorizerEnabled(true) - , mTextStart(20.0f) - , mLeftMargin(10) - , mCursorPositionChanged(false) - , mColorRangeMin(0) - , mColorRangeMax(0) - , mSelectionMode(SelectionMode::Normal) - , mCheckComments(true) - , mLastClick(-1.0f) - , mHandleKeyboardInputs(true) - , mHandleMouseInputs(true) - , mIgnoreImGuiChild(false) - , mShowWhitespaces(true) - , mStartTime(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + : mRegexList(std::make_shared()) { - SetPalette(GetDarkPalette()); - SetLanguageDefinition(LanguageDefinition::HLSL()); + SetPalette(defaultPalette); mLines.push_back(Line()); } @@ -58,92 +32,429 @@ TextEditor::~TextEditor() { } -void TextEditor::SetLanguageDefinition(const LanguageDefinition & aLanguageDef) +void TextEditor::SetPalette(PaletteId aValue) { - mLanguageDefinition = aLanguageDef; - mRegexList.clear(); + mPaletteId = aValue; + const Palette* palletteBase; + switch (mPaletteId) + { + case PaletteId::Dark: + palletteBase = &(GetDarkPalette()); + break; + case PaletteId::Light: + palletteBase = &(GetLightPalette()); + break; + case PaletteId::Mariana: + palletteBase = &(GetMarianaPalette()); + break; + case PaletteId::RetroBlue: + palletteBase = &(GetRetroBluePalette()); + break; + } + /* Update palette with the current alpha from style */ + for (int i = 0; i < (int)PaletteIndex::Max; ++i) + { + ImVec4 color = U32ColorToVec4((*palletteBase)[i]); + color.w *= ImGui::GetStyle().Alpha; + mPalette[i] = ImGui::ColorConvertFloat4ToU32(color); + } +} - for (auto& r : mLanguageDefinition.mTokenRegexStrings) - mRegexList.push_back(std::make_pair(std::regex(r.first, std::regex_constants::optimize), r.second)); +void TextEditor::SetPaletteIndex(PaletteIndex iIndex, ImU32 aValue) +{ + mPalette[int(iIndex)] = aValue; +} + +void TextEditor::SetLanguageDefinition(LanguageDefinitionId aValue) +{ + mLanguageDefinitionId = aValue; + switch (mLanguageDefinitionId) + { + case LanguageDefinitionId::None: + mLanguageDefinition = nullptr; + return; + case LanguageDefinitionId::Cpp: + mLanguageDefinition = &(LanguageDefinition::Cpp()); + break; + case LanguageDefinitionId::C: + mLanguageDefinition = &(LanguageDefinition::C()); + break; + case LanguageDefinitionId::Cs: + mLanguageDefinition = &(LanguageDefinition::Cs()); + break; + case LanguageDefinitionId::Python: + mLanguageDefinition = &(LanguageDefinition::Python()); + break; + case LanguageDefinitionId::Lua: + mLanguageDefinition = &(LanguageDefinition::Lua()); + break; + case LanguageDefinitionId::Json: + mLanguageDefinition = &(LanguageDefinition::Json()); + break; + case LanguageDefinitionId::Sql: + mLanguageDefinition = &(LanguageDefinition::Sql()); + break; + case LanguageDefinitionId::AngelScript: + mLanguageDefinition = &(LanguageDefinition::AngelScript()); + break; + case LanguageDefinitionId::Glsl: + mLanguageDefinition = &(LanguageDefinition::Glsl()); + break; + case LanguageDefinitionId::Hlsl: + mLanguageDefinition = &(LanguageDefinition::Hlsl()); + break; + } + + mRegexList->mValue.clear(); + for (const auto& r : mLanguageDefinition->mTokenRegexStrings) + mRegexList->mValue.push_back(std::make_pair(boost::regex(r.first, boost::regex_constants::optimize), r.second)); Colorize(); } -void TextEditor::SetPalette(const Palette & aValue) +const char* TextEditor::GetLanguageDefinitionName() const { - mPaletteBase = aValue; + return mLanguageDefinition != nullptr ? mLanguageDefinition->mName.c_str() : "None"; } -std::string TextEditor::GetText(const Coordinates & aStart, const Coordinates & aEnd) const +void TextEditor::SetTabSize(int aValue) { - std::string result; + mTabSize = Max(1, Min(8, aValue)); +} - auto lstart = aStart.mLine; - auto lend = aEnd.mLine; - auto istart = GetCharacterIndex(aStart); - auto iend = GetCharacterIndex(aEnd); - size_t s = 0; +void TextEditor::SetLineSpacing(float aValue) +{ + mLineSpacing = Max(1.0f, Min(2.0f, aValue)); +} - for (size_t i = lstart; i < lend; i++) - s += mLines[i].size(); +void TextEditor::SelectAll() +{ + ClearSelections(); + ClearExtraCursors(); + MoveTop(); + MoveBottom(true); +} - result.reserve(s + s / 8); +void TextEditor::SelectLine(int aLine) +{ + ClearSelections(); + ClearExtraCursors(); + SetSelection({ aLine, 0 }, { aLine, GetLineMaxColumn(aLine) }); +} - while (istart < iend || lstart < lend) +void TextEditor::SelectRegion(int aStartLine, int aStartChar, int aEndLine, int aEndChar) +{ + ClearSelections(); + ClearExtraCursors(); + SetSelection(aStartLine, aStartChar, aEndLine, aEndChar); +} + +void TextEditor::SelectNextOccurrenceOf(const char* aText, int aTextSize, bool aCaseSensitive) +{ + ClearSelections(); + ClearExtraCursors(); + SelectNextOccurrenceOf(aText, aTextSize, -1, aCaseSensitive); +} + +void TextEditor::SelectAllOccurrencesOf(const char* aText, int aTextSize, bool aCaseSensitive) +{ + ClearSelections(); + ClearExtraCursors(); + SelectNextOccurrenceOf(aText, aTextSize, -1, aCaseSensitive); + Coordinates startPos = mState.mCursors[mState.GetLastAddedCursorIndex()].mInteractiveEnd; + while (true) { - if (lstart >= (int)mLines.size()) + AddCursorForNextOccurrence(aCaseSensitive); + Coordinates lastAddedPos = mState.mCursors[mState.GetLastAddedCursorIndex()].mInteractiveEnd; + if (lastAddedPos == startPos) break; + } +} - auto& line = mLines[lstart]; - if (istart < (int)line.size()) +bool TextEditor::AnyCursorHasSelection() const +{ + for (int c = 0; c <= mState.mCurrentCursor; c++) + if (mState.mCursors[c].HasSelection()) + return true; + return false; +} + +bool TextEditor::AllCursorsHaveSelection() const +{ + for (int c = 0; c <= mState.mCurrentCursor; c++) + if (!mState.mCursors[c].HasSelection()) + return false; + return true; +} + +void TextEditor::ClearExtraCursors() +{ + mState.mCurrentCursor = 0; +} + +void TextEditor::ClearSelections() +{ + for (int c = mState.mCurrentCursor; c > -1; c--) + mState.mCursors[c].mInteractiveEnd = + mState.mCursors[c].mInteractiveStart = + mState.mCursors[c].GetSelectionEnd(); +} + +void TextEditor::SetCursorPosition(int aLine, int aCharIndex) +{ + SetCursorPosition({ aLine, GetCharacterColumn(aLine, aCharIndex) }, -1, true); +} + +int TextEditor::GetFirstVisibleLine() +{ + return mFirstVisibleLine; +} + +int TextEditor::GetLastVisibleLine() +{ + return mLastVisibleLine; +} + +void TextEditor::SetViewAtLine(int aLine, SetViewAtLineMode aMode) +{ + mSetViewAtLine = aLine; + mSetViewAtLineMode = aMode; +} + +void TextEditor::Copy() +{ + if (AnyCursorHasSelection()) + { + std::string clipboardText = GetClipboardText(); + ImGui::SetClipboardText(clipboardText.c_str()); + } + else + { + if (!mLines.empty()) { - result += line[istart].mChar; - istart++; + std::string str; + auto& line = mLines[GetSanitizedCursorCoordinates().mLine]; + for (auto& g : line) + str.push_back(g.mChar); + ImGui::SetClipboardText(str.c_str()); } + } +} + +void TextEditor::Cut() +{ + if (mReadOnly) + { + Copy(); + } + else + { + if (AnyCursorHasSelection()) + { + UndoRecord u; + u.mBefore = mState; + + Copy(); + for (int c = mState.mCurrentCursor; c > -1; c--) + { + u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].GetSelectionStart(), mState.mCursors[c].GetSelectionEnd(), UndoOperationType::Delete }); + DeleteSelection(c); + } + + u.mAfter = mState; + AddUndo(u); + } + } +} + +void TextEditor::Paste() +{ + if (mReadOnly) + return; + + if (ImGui::GetClipboardText() == nullptr) + return; // something other than text in the clipboard + + // check if we should do multicursor paste + std::string clipText = ImGui::GetClipboardText(); + bool canPasteToMultipleCursors = false; + std::vector> clipTextLines; + if (mState.mCurrentCursor > 0) + { + clipTextLines.push_back({ 0,0 }); + for (int i = 0; i < clipText.length(); i++) + { + if (clipText[i] == '\n') + { + clipTextLines.back().second = i; + clipTextLines.push_back({ i + 1, 0 }); + } + } + clipTextLines.back().second = clipText.length(); + canPasteToMultipleCursors = clipTextLines.size() == mState.mCurrentCursor + 1; + } + + if (clipText.length() > 0) + { + UndoRecord u; + u.mBefore = mState; + + if (AnyCursorHasSelection()) + { + for (int c = mState.mCurrentCursor; c > -1; c--) + { + u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].GetSelectionStart(), mState.mCursors[c].GetSelectionEnd(), UndoOperationType::Delete }); + DeleteSelection(c); + } + } + + for (int c = mState.mCurrentCursor; c > -1; c--) + { + Coordinates start = GetSanitizedCursorCoordinates(c); + if (canPasteToMultipleCursors) + { + std::string clipSubText = clipText.substr(clipTextLines[c].first, clipTextLines[c].second - clipTextLines[c].first); + InsertTextAtCursor(clipSubText.c_str(), c); + u.mOperations.push_back({ clipSubText, start, GetSanitizedCursorCoordinates(c), UndoOperationType::Add }); + } + else + { + InsertTextAtCursor(clipText.c_str(), c); + u.mOperations.push_back({ clipText, start, GetSanitizedCursorCoordinates(c), UndoOperationType::Add }); + } + } + + u.mAfter = mState; + AddUndo(u); + } +} + +void TextEditor::Undo(int aSteps) +{ + while (CanUndo() && aSteps-- > 0) + mUndoBuffer[--mUndoIndex].Undo(this); +} + +void TextEditor::Redo(int aSteps) +{ + while (CanRedo() && aSteps-- > 0) + mUndoBuffer[mUndoIndex++].Redo(this); +} + +void TextEditor::SetText(const std::string& aText) +{ + mLines.clear(); + mLines.emplace_back(Line()); + for (auto chr : aText) + { + if (chr == '\r') + continue; + + if (chr == '\n') + mLines.emplace_back(Line()); else { - istart = 0; - ++lstart; - result += '\n'; + mLines.back().emplace_back(Glyph(chr, PaletteIndex::Default)); } } + mScrollToTop = true; + + mUndoBuffer.clear(); + mUndoIndex = 0; + + Colorize(); +} + +std::string TextEditor::GetText() const +{ + auto lastLine = (int)mLines.size() - 1; + auto lastLineLength = GetLineMaxColumn(lastLine); + Coordinates startCoords = Coordinates(); + Coordinates endCoords = Coordinates(lastLine, lastLineLength); + return startCoords < endCoords ? GetText(startCoords, endCoords) : ""; +} + +void TextEditor::SetTextLines(const std::vector& aLines) +{ + mLines.clear(); + + if (aLines.empty()) + mLines.emplace_back(Line()); + else + { + mLines.resize(aLines.size()); + + for (size_t i = 0; i < aLines.size(); ++i) + { + const std::string& aLine = aLines[i]; + + mLines[i].reserve(aLine.size()); + for (size_t j = 0; j < aLine.size(); ++j) + mLines[i].emplace_back(Glyph(aLine[j], PaletteIndex::Default)); + } + } + + mScrollToTop = true; + + mUndoBuffer.clear(); + mUndoIndex = 0; + + Colorize(); +} + +std::vector TextEditor::GetTextLines() const +{ + std::vector result; + + result.reserve(mLines.size()); + + for (auto& line : mLines) + { + std::string text; + + text.resize(line.size()); + + for (size_t i = 0; i < line.size(); ++i) + text[i] = line[i].mChar; + + result.emplace_back(std::move(text)); + } + return result; } -TextEditor::Coordinates TextEditor::GetActualCursorCoordinates() const +bool TextEditor::Render(const char* aTitle, bool aParentIsFocused, const ImVec2& aSize, bool aBorder) { - return SanitizeCoordinates(mState.mCursorPosition); + if (mCursorPositionChanged) + OnCursorPositionChanged(); + mCursorPositionChanged = false; + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(mPalette[(int)PaletteIndex::Background])); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); + + ImGui::BeginChild(aTitle, aSize, aBorder, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNavInputs); + + bool isFocused = ImGui::IsWindowFocused(); + HandleKeyboardInputs(aParentIsFocused); + HandleMouseInputs(); + ColorizeInternal(); + Render(aParentIsFocused); + + ImGui::EndChild(); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + + return isFocused; } -TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates & aValue) const -{ - auto line = aValue.mLine; - auto column = aValue.mColumn; - if (line >= (int)mLines.size()) - { - if (mLines.empty()) - { - line = 0; - column = 0; - } - else - { - line = (int)mLines.size() - 1; - column = GetLineMaxColumn(line); - } - return Coordinates(line, column); - } - else - { - column = mLines.empty() ? 0 : std::min(column, GetLineMaxColumn(line)); - return Coordinates(line, column); - } -} +// ------------------------------------ // +// ---------- Generic utils ----------- // // https://en.wikipedia.org/wiki/UTF-8 // We assume that the char is a standalone character (<128) or a leading byte of an UTF-8 code sequence (non-10xxxxxx code) -static int UTF8CharLength(TextEditor::Char c) +static int UTF8CharLength(char c) { if ((c & 0xFE) == 0xFC) return 6; @@ -196,72 +507,203 @@ static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) } } -void TextEditor::Advance(Coordinates & aCoordinates) const +static inline bool CharIsWordChar(char ch) { - if (aCoordinates.mLine < (int)mLines.size()) - { - auto& line = mLines[aCoordinates.mLine]; - auto cindex = GetCharacterIndex(aCoordinates); + int sizeInBytes = UTF8CharLength(ch); + return sizeInBytes > 1 || + ch >= 'a' && ch <= 'z' || + ch >= 'A' && ch <= 'Z' || + ch >= '0' && ch <= '9' || + ch == '_'; +} - if (cindex + 1 < (int)line.size()) +// ------------------------------------ // +// ------------- Internal ------------- // + + +// ---------- Editor state functions --------- // + +void TextEditor::EditorState::AddCursor() +{ + // vector is never resized to smaller size, mCurrentCursor points to last available cursor in vector + mCurrentCursor++; + mCursors.resize(mCurrentCursor + 1); + mLastAddedCursor = mCurrentCursor; +} + +int TextEditor::EditorState::GetLastAddedCursorIndex() +{ + return mLastAddedCursor > mCurrentCursor ? 0 : mLastAddedCursor; +} + +void TextEditor::EditorState::SortCursorsFromTopToBottom() +{ + Coordinates lastAddedCursorPos = mCursors[GetLastAddedCursorIndex()].mInteractiveEnd; + std::sort(mCursors.begin(), mCursors.begin() + (mCurrentCursor + 1), [](const Cursor& a, const Cursor& b) -> bool { - auto delta = UTF8CharLength(line[cindex].mChar); - cindex = std::min(cindex + delta, (int)line.size() - 1); + return a.GetSelectionStart() < b.GetSelectionStart(); + }); + // update last added cursor index to be valid after sort + for (int c = mCurrentCursor; c > -1; c--) + if (mCursors[c].mInteractiveEnd == lastAddedCursorPos) + mLastAddedCursor = c; +} + +// ---------- Undo record functions --------- // + +TextEditor::UndoRecord::UndoRecord(const std::vector& aOperations, + TextEditor::EditorState& aBefore, TextEditor::EditorState& aAfter) +{ + mOperations = aOperations; + mBefore = aBefore; + mAfter = aAfter; + for (const UndoOperation& o : mOperations) + assert(o.mStart <= o.mEnd); +} + +void TextEditor::UndoRecord::Undo(TextEditor* aEditor) +{ + for (int i = mOperations.size() - 1; i > -1; i--) + { + const UndoOperation& operation = mOperations[i]; + if (!operation.mText.empty()) + { + switch (operation.mType) + { + case UndoOperationType::Delete: + { + auto start = operation.mStart; + aEditor->InsertTextAt(start, operation.mText.c_str()); + aEditor->Colorize(operation.mStart.mLine - 1, operation.mEnd.mLine - operation.mStart.mLine + 2); + break; + } + case UndoOperationType::Add: + { + aEditor->DeleteRange(operation.mStart, operation.mEnd); + aEditor->Colorize(operation.mStart.mLine - 1, operation.mEnd.mLine - operation.mStart.mLine + 2); + break; + } + } + } + } + + aEditor->mState = mBefore; + aEditor->EnsureCursorVisible(); +} + +void TextEditor::UndoRecord::Redo(TextEditor* aEditor) +{ + for (int i = 0; i < mOperations.size(); i++) + { + const UndoOperation& operation = mOperations[i]; + if (!operation.mText.empty()) + { + switch (operation.mType) + { + case UndoOperationType::Delete: + { + aEditor->DeleteRange(operation.mStart, operation.mEnd); + aEditor->Colorize(operation.mStart.mLine - 1, operation.mEnd.mLine - operation.mStart.mLine + 1); + break; + } + case UndoOperationType::Add: + { + auto start = operation.mStart; + aEditor->InsertTextAt(start, operation.mText.c_str()); + aEditor->Colorize(operation.mStart.mLine - 1, operation.mEnd.mLine - operation.mStart.mLine + 1); + break; + } + } + } + } + + aEditor->mState = mAfter; + aEditor->EnsureCursorVisible(); +} + +// ---------- Text editor internal functions --------- // + +std::string TextEditor::GetText(const Coordinates& aStart, const Coordinates& aEnd) const +{ + assert(aStart < aEnd); + + std::string result; + auto lstart = aStart.mLine; + auto lend = aEnd.mLine; + auto istart = GetCharacterIndexR(aStart); + auto iend = GetCharacterIndexR(aEnd); + size_t s = 0; + + for (size_t i = lstart; i < lend; i++) + s += mLines[i].size(); + + result.reserve(s + s / 8); + + while (istart < iend || lstart < lend) + { + if (lstart >= (int)mLines.size()) + break; + + auto& line = mLines[lstart]; + if (istart < (int)line.size()) + { + result += line[istart].mChar; + istart++; } else { - ++aCoordinates.mLine; - cindex = 0; + istart = 0; + ++lstart; + result += '\n'; } - aCoordinates.mColumn = GetCharacterColumn(aCoordinates.mLine, cindex); + } + + return result; +} + +std::string TextEditor::GetClipboardText() const +{ + std::string result; + for (int c = 0; c <= mState.mCurrentCursor; c++) + { + if (mState.mCursors[c].GetSelectionStart() < mState.mCursors[c].GetSelectionEnd()) + { + if (result.length() != 0) + result += '\n'; + result += GetText(mState.mCursors[c].GetSelectionStart(), mState.mCursors[c].GetSelectionEnd()); + } + } + return result; +} + +std::string TextEditor::GetSelectedText(int aCursor) const +{ + if (aCursor == -1) + aCursor = mState.mCurrentCursor; + + return GetText(mState.mCursors[aCursor].GetSelectionStart(), mState.mCursors[aCursor].GetSelectionEnd()); +} + +void TextEditor::SetCursorPosition(const Coordinates& aPosition, int aCursor, bool aClearSelection) +{ + if (aCursor == -1) + aCursor = mState.mCurrentCursor; + + mCursorPositionChanged = true; + if (aClearSelection) + mState.mCursors[aCursor].mInteractiveStart = aPosition; + if (mState.mCursors[aCursor].mInteractiveEnd != aPosition) + { + mState.mCursors[aCursor].mInteractiveEnd = aPosition; + EnsureCursorVisible(); } } -void TextEditor::DeleteRange(const Coordinates & aStart, const Coordinates & aEnd) -{ - assert(aEnd >= aStart); - assert(!mReadOnly); - - //printf("D(%d.%d)-(%d.%d)\n", aStart.mLine, aStart.mColumn, aEnd.mLine, aEnd.mColumn); - - if (aEnd == aStart) - return; - - auto start = GetCharacterIndex(aStart); - auto end = GetCharacterIndex(aEnd); - - if (aStart.mLine == aEnd.mLine) - { - auto& line = mLines[aStart.mLine]; - auto n = GetLineMaxColumn(aStart.mLine); - if (aEnd.mColumn >= n) - line.erase(line.begin() + start, line.end()); - else - line.erase(line.begin() + start, line.begin() + end); - } - else - { - auto& firstLine = mLines[aStart.mLine]; - auto& lastLine = mLines[aEnd.mLine]; - - firstLine.erase(firstLine.begin() + start, firstLine.end()); - lastLine.erase(lastLine.begin(), lastLine.begin() + end); - - if (aStart.mLine < aEnd.mLine) - firstLine.insert(firstLine.end(), lastLine.begin(), lastLine.end()); - - if (aStart.mLine < aEnd.mLine) - RemoveLine(aStart.mLine + 1, aEnd.mLine + 1); - } - - mTextChanged = true; -} - -int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char * aValue) +int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char* aValue) { assert(!mReadOnly); - int cindex = GetCharacterIndex(aWhere); + int cindex = GetCharacterIndexR(aWhere); int totalLines = 0; while (*aValue != '\0') { @@ -278,8 +720,8 @@ int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char * aValu { auto& newLine = InsertLine(aWhere.mLine + 1); auto& line = mLines[aWhere.mLine]; - newLine.insert(newLine.begin(), line.begin() + cindex, line.end()); - line.erase(line.begin() + cindex, line.end()); + AddGlyphsToLine(aWhere.mLine + 1, 0, line.begin() + cindex, line.end()); + RemoveGlyphsFromLine(aWhere.mLine, cindex); } else { @@ -296,387 +738,1225 @@ int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char * aValu auto& line = mLines[aWhere.mLine]; auto d = UTF8CharLength(*aValue); while (d-- > 0 && *aValue != '\0') - line.insert(line.begin() + cindex++, Glyph(*aValue++, PaletteIndex::Default)); - ++aWhere.mColumn; + AddGlyphToLine(aWhere.mLine, cindex++, Glyph(*aValue++, PaletteIndex::Default)); + aWhere.mColumn = GetCharacterColumn(aWhere.mLine, cindex); } - - mTextChanged = true; } return totalLines; } -void TextEditor::AddUndo(UndoRecord& aValue) +void TextEditor::InsertTextAtCursor(const char* aValue, int aCursor) { - assert(!mReadOnly); - //printf("AddUndo: (@%d.%d) +\'%s' [%d.%d .. %d.%d], -\'%s', [%d.%d .. %d.%d] (@%d.%d)\n", - // aValue.mBefore.mCursorPosition.mLine, aValue.mBefore.mCursorPosition.mColumn, - // aValue.mAdded.c_str(), aValue.mAddedStart.mLine, aValue.mAddedStart.mColumn, aValue.mAddedEnd.mLine, aValue.mAddedEnd.mColumn, - // aValue.mRemoved.c_str(), aValue.mRemovedStart.mLine, aValue.mRemovedStart.mColumn, aValue.mRemovedEnd.mLine, aValue.mRemovedEnd.mColumn, - // aValue.mAfter.mCursorPosition.mLine, aValue.mAfter.mCursorPosition.mColumn - // ); + if (aValue == nullptr) + return; + if (aCursor == -1) + aCursor = mState.mCurrentCursor; - mUndoBuffer.resize((size_t)(mUndoIndex + 1)); - mUndoBuffer.back() = aValue; - ++mUndoIndex; + auto pos = GetSanitizedCursorCoordinates(aCursor); + auto start = std::min(pos, mState.mCursors[aCursor].GetSelectionStart()); + int totalLines = pos.mLine - start.mLine; + + totalLines += InsertTextAt(pos, aValue); + + SetCursorPosition(pos, aCursor); + Colorize(start.mLine - 1, totalLines + 2); } -TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2& aPosition) const +bool TextEditor::Move(int& aLine, int& aCharIndex, bool aLeft, bool aLockLine) const { - ImVec2 origin = ImGui::GetCursorScreenPos(); - ImVec2 local(aPosition.x - origin.x, aPosition.y - origin.y); + // assumes given char index is not in the middle of utf8 sequence + // char index can be line.length() - int lineNo = std::max(0, (int)floor(local.y / mCharAdvance.y)); + // invalid line + if (aLine >= mLines.size()) + return false; - int columnCoord = 0; - - if (lineNo >= 0 && lineNo < (int)mLines.size()) + if (aLeft) { - auto& line = mLines.at(lineNo); - - int columnIndex = 0; - float columnX = 0.0f; - - while ((size_t)columnIndex < line.size()) + if (aCharIndex == 0) { - float columnWidth = 0.0f; + if (aLockLine || aLine == 0) + return false; + aLine--; + aCharIndex = mLines[aLine].size(); + } + else + { + aCharIndex--; + while (aCharIndex > 0 && IsUTFSequence(mLines[aLine][aCharIndex].mChar)) + aCharIndex--; + } + } + else // right + { + if (aCharIndex == mLines[aLine].size()) + { + if (aLockLine || aLine == mLines.size() - 1) + return false; + aLine++; + aCharIndex = 0; + } + else + { + int seqLength = UTF8CharLength(mLines[aLine][aCharIndex].mChar); + aCharIndex = std::min(aCharIndex + seqLength, (int)mLines[aLine].size()); + } + } + return true; +} - if (line[columnIndex].mChar == '\t') +void TextEditor::MoveCharIndexAndColumn(int aLine, int& aCharIndex, int& aColumn) const +{ + assert(aLine < mLines.size()); + assert(aCharIndex < mLines[aLine].size()); + char c = mLines[aLine][aCharIndex].mChar; + aCharIndex += UTF8CharLength(c); + if (c == '\t') + aColumn = (aColumn / mTabSize) * mTabSize + mTabSize; + else + aColumn++; +} + +void TextEditor::MoveCoords(Coordinates& aCoords, MoveDirection aDirection, bool aWordMode, int aLineCount) const +{ + int charIndex = GetCharacterIndexR(aCoords); + int lineIndex = aCoords.mLine; + switch (aDirection) + { + case MoveDirection::Right: + if (charIndex >= mLines[lineIndex].size()) + { + if (lineIndex < mLines.size() - 1) { - float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ").x; - float oldX = columnX; - float newColumnX = (1.0f + std::floor((1.0f + columnX) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); - columnWidth = newColumnX - oldX; - if (mTextStart + columnX + columnWidth * 0.5f > local.x) - break; - columnX = newColumnX; - columnCoord = (columnCoord / mTabSize) * mTabSize + mTabSize; - columnIndex++; + aCoords.mLine = std::max(0, std::min((int)mLines.size() - 1, lineIndex + 1)); + aCoords.mColumn = 0; + } + } + else + { + Move(lineIndex, charIndex); + int oneStepRightColumn = GetCharacterColumn(lineIndex, charIndex); + if (aWordMode) + { + aCoords = FindWordEnd(aCoords); + aCoords.mColumn = std::max(aCoords.mColumn, oneStepRightColumn); + } + else + aCoords.mColumn = oneStepRightColumn; + } + break; + case MoveDirection::Left: + if (charIndex == 0) + { + if (lineIndex > 0) + { + aCoords.mLine = lineIndex - 1; + aCoords.mColumn = GetLineMaxColumn(aCoords.mLine); + } + } + else + { + Move(lineIndex, charIndex, true); + aCoords.mColumn = GetCharacterColumn(lineIndex, charIndex); + if (aWordMode) + aCoords = FindWordStart(aCoords); + } + break; + case MoveDirection::Up: + aCoords.mLine = std::max(0, lineIndex - aLineCount); + break; + case MoveDirection::Down: + aCoords.mLine = std::max(0, std::min((int)mLines.size() - 1, lineIndex + aLineCount)); + break; + } +} + +void TextEditor::MoveUp(int aAmount, bool aSelect) +{ + for (int c = 0; c <= mState.mCurrentCursor; c++) + { + Coordinates newCoords = mState.mCursors[c].mInteractiveEnd; + MoveCoords(newCoords, MoveDirection::Up, false, aAmount); + SetCursorPosition(newCoords, c, !aSelect); + } + EnsureCursorVisible(); +} + +void TextEditor::MoveDown(int aAmount, bool aSelect) +{ + for (int c = 0; c <= mState.mCurrentCursor; c++) + { + assert(mState.mCursors[c].mInteractiveEnd.mColumn >= 0); + Coordinates newCoords = mState.mCursors[c].mInteractiveEnd; + MoveCoords(newCoords, MoveDirection::Down, false, aAmount); + SetCursorPosition(newCoords, c, !aSelect); + } + EnsureCursorVisible(); +} + +void TextEditor::MoveLeft(bool aSelect, bool aWordMode) +{ + if (mLines.empty()) + return; + + if (AnyCursorHasSelection() && !aSelect && !aWordMode) + { + for (int c = 0; c <= mState.mCurrentCursor; c++) + SetCursorPosition(mState.mCursors[c].GetSelectionStart(), c); + } + else + { + for (int c = 0; c <= mState.mCurrentCursor; c++) + { + Coordinates newCoords = mState.mCursors[c].mInteractiveEnd; + MoveCoords(newCoords, MoveDirection::Left, aWordMode); + SetCursorPosition(newCoords, c, !aSelect); + } + } + EnsureCursorVisible(); +} + +void TextEditor::MoveRight(bool aSelect, bool aWordMode) +{ + if (mLines.empty()) + return; + + if (AnyCursorHasSelection() && !aSelect && !aWordMode) + { + for (int c = 0; c <= mState.mCurrentCursor; c++) + SetCursorPosition(mState.mCursors[c].GetSelectionEnd(), c); + } + else + { + for (int c = 0; c <= mState.mCurrentCursor; c++) + { + Coordinates newCoords = mState.mCursors[c].mInteractiveEnd; + MoveCoords(newCoords, MoveDirection::Right, aWordMode); + SetCursorPosition(newCoords, c, !aSelect); + } + } + EnsureCursorVisible(); +} + +void TextEditor::MoveTop(bool aSelect) +{ + SetCursorPosition(Coordinates(0, 0), mState.mCurrentCursor, !aSelect); +} + +void TextEditor::TextEditor::MoveBottom(bool aSelect) +{ + int maxLine = (int)mLines.size() - 1; + Coordinates newPos = Coordinates(maxLine, GetLineMaxColumn(maxLine)); + SetCursorPosition(newPos, mState.mCurrentCursor, !aSelect); +} + +void TextEditor::MoveHome(bool aSelect) +{ + for (int c = 0; c <= mState.mCurrentCursor; c++) + SetCursorPosition(Coordinates(mState.mCursors[c].mInteractiveEnd.mLine, 0), c, !aSelect); +} + +void TextEditor::MoveEnd(bool aSelect) +{ + for (int c = 0; c <= mState.mCurrentCursor; c++) + { + int lindex = mState.mCursors[c].mInteractiveEnd.mLine; + SetCursorPosition(Coordinates(lindex, GetLineMaxColumn(lindex)), c, !aSelect); + } +} + +void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) +{ + assert(!mReadOnly); + + bool hasSelection = AnyCursorHasSelection(); + bool anyCursorHasMultilineSelection = false; + for (int c = mState.mCurrentCursor; c > -1; c--) + if (mState.mCursors[c].GetSelectionStart().mLine != mState.mCursors[c].GetSelectionEnd().mLine) + { + anyCursorHasMultilineSelection = true; + break; + } + bool isIndentOperation = hasSelection && anyCursorHasMultilineSelection && aChar == '\t'; + if (isIndentOperation) + { + ChangeCurrentLinesIndentation(!aShift); + return; + } + + UndoRecord u; + u.mBefore = mState; + + if (hasSelection) + { + for (int c = mState.mCurrentCursor; c > -1; c--) + { + u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].GetSelectionStart(), mState.mCursors[c].GetSelectionEnd(), UndoOperationType::Delete }); + DeleteSelection(c); + } + } + + std::vector coords; + for (int c = mState.mCurrentCursor; c > -1; c--) // order important here for typing \n in the same line at the same time + { + auto coord = GetSanitizedCursorCoordinates(c); + coords.push_back(coord); + UndoOperation added; + added.mType = UndoOperationType::Add; + added.mStart = coord; + + assert(!mLines.empty()); + + if (aChar == '\n') + { + InsertLine(coord.mLine + 1); + auto& line = mLines[coord.mLine]; + auto& newLine = mLines[coord.mLine + 1]; + + added.mText = ""; + added.mText += (char)aChar; + if (mAutoIndent) + for (int i = 0; i < line.size() && isascii(line[i].mChar) && isblank(line[i].mChar); ++i) + { + newLine.push_back(line[i]); + added.mText += line[i].mChar; + } + + const size_t whitespaceSize = newLine.size(); + auto cindex = GetCharacterIndexR(coord); + AddGlyphsToLine(coord.mLine + 1, newLine.size(), line.begin() + cindex, line.end()); + RemoveGlyphsFromLine(coord.mLine, cindex); + SetCursorPosition(Coordinates(coord.mLine + 1, GetCharacterColumn(coord.mLine + 1, (int)whitespaceSize)), c); + } + else + { + char buf[7]; + int e = ImTextCharToUtf8(buf, 7, aChar); + if (e > 0) + { + buf[e] = '\0'; + auto& line = mLines[coord.mLine]; + auto cindex = GetCharacterIndexR(coord); + + for (auto p = buf; *p != '\0'; p++, ++cindex) + AddGlyphToLine(coord.mLine, cindex, Glyph(*p, PaletteIndex::Default)); + added.mText = buf; + + SetCursorPosition(Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex)), c); + } + else + continue; + } + + added.mEnd = GetSanitizedCursorCoordinates(c); + u.mOperations.push_back(added); + } + + u.mAfter = mState; + AddUndo(u); + + for (const auto& coord : coords) + Colorize(coord.mLine - 1, 3); + EnsureCursorVisible(); +} + +void TextEditor::Backspace(bool aWordMode) +{ + assert(!mReadOnly); + + if (mLines.empty()) + return; + + if (AnyCursorHasSelection()) + Delete(aWordMode); + else + { + EditorState stateBeforeDeleting = mState; + MoveLeft(true, aWordMode); + if (!AllCursorsHaveSelection()) // can't do backspace if any cursor at {0,0} + { + if (AnyCursorHasSelection()) + MoveRight(); + return; + } + + OnCursorPositionChanged(); // might combine cursors + Delete(aWordMode, &stateBeforeDeleting); + } +} + +void TextEditor::Delete(bool aWordMode, const EditorState* aEditorState) +{ + assert(!mReadOnly); + + if (mLines.empty()) + return; + + if (AnyCursorHasSelection()) + { + UndoRecord u; + u.mBefore = aEditorState == nullptr ? mState : *aEditorState; + for (int c = mState.mCurrentCursor; c > -1; c--) + { + if (!mState.mCursors[c].HasSelection()) + continue; + u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].GetSelectionStart(), mState.mCursors[c].GetSelectionEnd(), UndoOperationType::Delete }); + DeleteSelection(c); + } + u.mAfter = mState; + AddUndo(u); + } + else + { + EditorState stateBeforeDeleting = mState; + MoveRight(true, aWordMode); + if (!AllCursorsHaveSelection()) // can't do delete if any cursor at end of last line + { + if (AnyCursorHasSelection()) + MoveLeft(); + return; + } + + OnCursorPositionChanged(); // might combine cursors + Delete(aWordMode, &stateBeforeDeleting); + } +} + +void TextEditor::SetSelection(Coordinates aStart, Coordinates aEnd, int aCursor) +{ + if (aCursor == -1) + aCursor = mState.mCurrentCursor; + + Coordinates minCoords = Coordinates(0, 0); + int maxLine = (int)mLines.size() - 1; + Coordinates maxCoords = Coordinates(maxLine, GetLineMaxColumn(maxLine)); + if (aStart < minCoords) + aStart = minCoords; + else if (aStart > maxCoords) + aStart = maxCoords; + if (aEnd < minCoords) + aEnd = minCoords; + else if (aEnd > maxCoords) + aEnd = maxCoords; + + mState.mCursors[aCursor].mInteractiveStart = aStart; + SetCursorPosition(aEnd, aCursor, false); +} + +void TextEditor::SetSelection(int aStartLine, int aStartChar, int aEndLine, int aEndChar, int aCursor) +{ + Coordinates startCoords = { aStartLine, GetCharacterColumn(aStartLine, aStartChar) }; + Coordinates endCoords = { aEndLine, GetCharacterColumn(aEndLine, aEndChar) }; + SetSelection(startCoords, endCoords, aCursor); +} + +void TextEditor::SelectNextOccurrenceOf(const char* aText, int aTextSize, int aCursor, bool aCaseSensitive) +{ + if (aCursor == -1) + aCursor = mState.mCurrentCursor; + Coordinates nextStart, nextEnd; + FindNextOccurrence(aText, aTextSize, mState.mCursors[aCursor].mInteractiveEnd, nextStart, nextEnd, aCaseSensitive); + SetSelection(nextStart, nextEnd, aCursor); + EnsureCursorVisible(aCursor, true); +} + +void TextEditor::AddCursorForNextOccurrence(bool aCaseSensitive) +{ + const Cursor& currentCursor = mState.mCursors[mState.GetLastAddedCursorIndex()]; + if (currentCursor.GetSelectionStart() == currentCursor.GetSelectionEnd()) + return; + + std::string selectionText = GetText(currentCursor.GetSelectionStart(), currentCursor.GetSelectionEnd()); + Coordinates nextStart, nextEnd; + if (!FindNextOccurrence(selectionText.c_str(), selectionText.length(), currentCursor.GetSelectionEnd(), nextStart, nextEnd, aCaseSensitive)) + return; + + mState.AddCursor(); + SetSelection(nextStart, nextEnd, mState.mCurrentCursor); + mState.SortCursorsFromTopToBottom(); + MergeCursorsIfPossible(); + EnsureCursorVisible(-1, true); +} + +bool TextEditor::FindNextOccurrence(const char* aText, int aTextSize, const Coordinates& aFrom, Coordinates& outStart, Coordinates& outEnd, bool aCaseSensitive) +{ + assert(aTextSize > 0); + bool fmatches = false; + int fline, ifline; + int findex, ifindex; + + ifline = fline = aFrom.mLine; + ifindex = findex = GetCharacterIndexR(aFrom); + + while (true) + { + bool matches; + { // match function + int lineOffset = 0; + int currentCharIndex = findex; + int i = 0; + for (; i < aTextSize; i++) + { + if (currentCharIndex == mLines[fline + lineOffset].size()) + { + if (aText[i] == '\n' && fline + lineOffset + 1 < mLines.size()) + { + currentCharIndex = 0; + lineOffset++; + } + else + break; + } + else + { + char toCompareA = mLines[fline + lineOffset][currentCharIndex].mChar; + char toCompareB = aText[i]; + toCompareA = (!aCaseSensitive && toCompareA >= 'A' && toCompareA <= 'Z') ? toCompareA - 'A' + 'a' : toCompareA; + toCompareB = (!aCaseSensitive && toCompareB >= 'A' && toCompareB <= 'Z') ? toCompareB - 'A' + 'a' : toCompareB; + if (toCompareA != toCompareB) + break; + else + currentCharIndex++; + } + } + matches = i == aTextSize; + if (matches) + { + outStart = { fline, GetCharacterColumn(fline, findex) }; + outEnd = { fline + lineOffset, GetCharacterColumn(fline + lineOffset, currentCharIndex) }; + return true; + } + } + + // move forward + if (findex == mLines[fline].size()) // need to consider line breaks + { + if (fline == mLines.size() - 1) + { + fline = 0; + findex = 0; } else { - char buf[7]; - auto d = UTF8CharLength(line[columnIndex].mChar); - int i = 0; - while (i < 6 && d-- > 0) - buf[i++] = line[columnIndex++].mChar; - buf[i] = '\0'; - columnWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf).x; - if (mTextStart + columnX + columnWidth * 0.5f > local.x) - break; - columnX += columnWidth; - columnCoord++; + fline++; + findex = 0; } } + else + findex++; + + // detect complete scan + if (findex == ifindex && fline == ifline) + return false; } - return SanitizeCoordinates(Coordinates(lineNo, columnCoord)); + return false; } -TextEditor::Coordinates TextEditor::FindWordStart(const Coordinates & aFrom) const +bool TextEditor::FindMatchingBracket(int aLine, int aCharIndex, Coordinates& out) { - Coordinates at = aFrom; - if (at.mLine >= (int)mLines.size()) - return at; + if (aLine > mLines.size() - 1) + return false; + int maxCharIndex = mLines[aLine].size() - 1; + if (aCharIndex > maxCharIndex) + return false; - auto& line = mLines[at.mLine]; - auto cindex = GetCharacterIndex(at); - - if (cindex >= (int)line.size()) - return at; - - while (cindex > 0 && isspace(line[cindex].mChar)) - --cindex; - - auto cstart = (PaletteIndex)line[cindex].mColorIndex; - while (cindex > 0) + int currentLine = aLine; + int currentCharIndex = aCharIndex; + int counter = 1; + if (CLOSE_TO_OPEN_CHAR.find(mLines[aLine][aCharIndex].mChar) != CLOSE_TO_OPEN_CHAR.end()) { - auto c = line[cindex].mChar; - if ((c & 0xC0) != 0x80) // not UTF code sequence 10xxxxxx + char closeChar = mLines[aLine][aCharIndex].mChar; + char openChar = CLOSE_TO_OPEN_CHAR.at(closeChar); + while (Move(currentLine, currentCharIndex, true)) { - if (c <= 32 && isspace(c)) + if (currentCharIndex < mLines[currentLine].size()) { - cindex++; - break; + char currentChar = mLines[currentLine][currentCharIndex].mChar; + if (currentChar == openChar) + { + counter--; + if (counter == 0) + { + out = { currentLine, GetCharacterColumn(currentLine, currentCharIndex) }; + return true; + } + } + else if (currentChar == closeChar) + counter++; } - if (cstart != (PaletteIndex)line[size_t(cindex - 1)].mColorIndex) - break; } - --cindex; } - return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex)); + else if (OPEN_TO_CLOSE_CHAR.find(mLines[aLine][aCharIndex].mChar) != OPEN_TO_CLOSE_CHAR.end()) + { + char openChar = mLines[aLine][aCharIndex].mChar; + char closeChar = OPEN_TO_CLOSE_CHAR.at(openChar); + while (Move(currentLine, currentCharIndex)) + { + if (currentCharIndex < mLines[currentLine].size()) + { + char currentChar = mLines[currentLine][currentCharIndex].mChar; + if (currentChar == closeChar) + { + counter--; + if (counter == 0) + { + out = { currentLine, GetCharacterColumn(currentLine, currentCharIndex) }; + return true; + } + } + else if (currentChar == openChar) + counter++; + } + } + } + return false; } -TextEditor::Coordinates TextEditor::FindWordEnd(const Coordinates & aFrom) const +void TextEditor::ChangeCurrentLinesIndentation(bool aIncrease) { - Coordinates at = aFrom; - if (at.mLine >= (int)mLines.size()) - return at; + assert(!mReadOnly); - auto& line = mLines[at.mLine]; - auto cindex = GetCharacterIndex(at); + UndoRecord u; + u.mBefore = mState; - if (cindex >= (int)line.size()) - return at; - - bool prevspace = (bool)isspace(line[cindex].mChar); - auto cstart = (PaletteIndex)line[cindex].mColorIndex; - while (cindex < (int)line.size()) + for (int c = mState.mCurrentCursor; c > -1; c--) { - auto c = line[cindex].mChar; - auto d = UTF8CharLength(c); - if (cstart != (PaletteIndex)line[cindex].mColorIndex) - break; - - if (prevspace != !!isspace(c)) + for (int currentLine = mState.mCursors[c].GetSelectionEnd().mLine; currentLine >= mState.mCursors[c].GetSelectionStart().mLine; currentLine--) { - if (isspace(c)) - while (cindex < (int)line.size() && isspace(line[cindex].mChar)) - ++cindex; - break; + if (Coordinates{ currentLine, 0 } == mState.mCursors[c].GetSelectionEnd() && mState.mCursors[c].GetSelectionEnd() != mState.mCursors[c].GetSelectionStart()) // when selection ends at line start + continue; + + if (aIncrease) + { + if (mLines[currentLine].size() > 0) + { + Coordinates lineStart = { currentLine, 0 }; + Coordinates insertionEnd = lineStart; + InsertTextAt(insertionEnd, "\t"); // sets insertion end + u.mOperations.push_back({ "\t", lineStart, insertionEnd, UndoOperationType::Add }); + Colorize(lineStart.mLine, 1); + } + } + else + { + Coordinates start = { currentLine, 0 }; + Coordinates end = { currentLine, mTabSize }; + int charIndex = GetCharacterIndexL(end) - 1; + while (charIndex > -1 && (mLines[currentLine][charIndex].mChar == ' ' || mLines[currentLine][charIndex].mChar == '\t')) charIndex--; + bool onlySpaceCharactersFound = charIndex == -1; + if (onlySpaceCharactersFound) + { + u.mOperations.push_back({ GetText(start, end), start, end, UndoOperationType::Delete }); + DeleteRange(start, end); + Colorize(currentLine, 1); + } + } } - cindex += d; } - return Coordinates(aFrom.mLine, GetCharacterColumn(aFrom.mLine, cindex)); + + if (u.mOperations.size() > 0) + AddUndo(u); } -TextEditor::Coordinates TextEditor::FindNextWord(const Coordinates & aFrom) const +void TextEditor::MoveUpCurrentLines() { - Coordinates at = aFrom; - if (at.mLine >= (int)mLines.size()) - return at; + assert(!mReadOnly); - // skip to the next non-word character - auto cindex = GetCharacterIndex(aFrom); - bool isword = false; - bool skip = false; - if (cindex < (int)mLines[at.mLine].size()) + UndoRecord u; + u.mBefore = mState; + + std::set affectedLines; + int minLine = -1; + int maxLine = -1; + for (int c = mState.mCurrentCursor; c > -1; c--) // cursors are expected to be sorted from top to bottom { - auto& line = mLines[at.mLine]; - isword = isalnum(line[cindex].mChar); - skip = isword; + for (int currentLine = mState.mCursors[c].GetSelectionEnd().mLine; currentLine >= mState.mCursors[c].GetSelectionStart().mLine; currentLine--) + { + if (Coordinates{ currentLine, 0 } == mState.mCursors[c].GetSelectionEnd() && mState.mCursors[c].GetSelectionEnd() != mState.mCursors[c].GetSelectionStart()) // when selection ends at line start + continue; + affectedLines.insert(currentLine); + minLine = minLine == -1 ? currentLine : (currentLine < minLine ? currentLine : minLine); + maxLine = maxLine == -1 ? currentLine : (currentLine > maxLine ? currentLine : maxLine); + } + } + if (minLine == 0) // can't move up anymore + return; + + Coordinates start = { minLine - 1, 0 }; + Coordinates end = { maxLine, GetLineMaxColumn(maxLine) }; + u.mOperations.push_back({ GetText(start, end), start, end, UndoOperationType::Delete }); + + for (int line : affectedLines) // lines should be sorted here + std::swap(mLines[line - 1], mLines[line]); + for (int c = mState.mCurrentCursor; c > -1; c--) + { + mState.mCursors[c].mInteractiveStart.mLine -= 1; + mState.mCursors[c].mInteractiveEnd.mLine -= 1; + // no need to set mCursorPositionChanged as cursors will remain sorted } - while (!isword || skip) + end = { maxLine, GetLineMaxColumn(maxLine) }; // this line is swapped with line above, need to find new max column + u.mOperations.push_back({ GetText(start, end), start, end, UndoOperationType::Add }); + u.mAfter = mState; + AddUndo(u); +} + +void TextEditor::MoveDownCurrentLines() +{ + assert(!mReadOnly); + + UndoRecord u; + u.mBefore = mState; + + std::set affectedLines; + int minLine = -1; + int maxLine = -1; + for (int c = 0; c <= mState.mCurrentCursor; c++) // cursors are expected to be sorted from top to bottom { - if (at.mLine >= mLines.size()) + for (int currentLine = mState.mCursors[c].GetSelectionEnd().mLine; currentLine >= mState.mCursors[c].GetSelectionStart().mLine; currentLine--) { - auto l = std::max(0, (int) mLines.size() - 1); - return Coordinates(l, GetLineMaxColumn(l)); + if (Coordinates{ currentLine, 0 } == mState.mCursors[c].GetSelectionEnd() && mState.mCursors[c].GetSelectionEnd() != mState.mCursors[c].GetSelectionStart()) // when selection ends at line start + continue; + affectedLines.insert(currentLine); + minLine = minLine == -1 ? currentLine : (currentLine < minLine ? currentLine : minLine); + maxLine = maxLine == -1 ? currentLine : (currentLine > maxLine ? currentLine : maxLine); } + } + if (maxLine == mLines.size() - 1) // can't move down anymore + return; - auto& line = mLines[at.mLine]; - if (cindex < (int)line.size()) + Coordinates start = { minLine, 0 }; + Coordinates end = { maxLine + 1, GetLineMaxColumn(maxLine + 1) }; + u.mOperations.push_back({ GetText(start, end), start, end, UndoOperationType::Delete }); + + std::set::reverse_iterator rit; + for (rit = affectedLines.rbegin(); rit != affectedLines.rend(); rit++) // lines should be sorted here + std::swap(mLines[*rit + 1], mLines[*rit]); + for (int c = mState.mCurrentCursor; c > -1; c--) + { + mState.mCursors[c].mInteractiveStart.mLine += 1; + mState.mCursors[c].mInteractiveEnd.mLine += 1; + // no need to set mCursorPositionChanged as cursors will remain sorted + } + + end = { maxLine + 1, GetLineMaxColumn(maxLine + 1) }; // this line is swapped with line below, need to find new max column + u.mOperations.push_back({ GetText(start, end), start, end, UndoOperationType::Add }); + u.mAfter = mState; + AddUndo(u); +} + +void TextEditor::ToggleLineComment() +{ + assert(!mReadOnly); + if (mLanguageDefinition == nullptr) + return; + const std::string& commentString = mLanguageDefinition->mSingleLineComment; + + UndoRecord u; + u.mBefore = mState; + + bool shouldAddComment = false; + std::unordered_set affectedLines; + for (int c = mState.mCurrentCursor; c > -1; c--) + { + for (int currentLine = mState.mCursors[c].GetSelectionEnd().mLine; currentLine >= mState.mCursors[c].GetSelectionStart().mLine; currentLine--) { - isword = isalnum(line[cindex].mChar); + if (Coordinates{ currentLine, 0 } == mState.mCursors[c].GetSelectionEnd() && mState.mCursors[c].GetSelectionEnd() != mState.mCursors[c].GetSelectionStart()) // when selection ends at line start + continue; + affectedLines.insert(currentLine); + int currentIndex = 0; + while (currentIndex < mLines[currentLine].size() && (mLines[currentLine][currentIndex].mChar == ' ' || mLines[currentLine][currentIndex].mChar == '\t')) currentIndex++; + if (currentIndex == mLines[currentLine].size()) + continue; + int i = 0; + while (i < commentString.length() && currentIndex + i < mLines[currentLine].size() && mLines[currentLine][currentIndex + i].mChar == commentString[i]) i++; + bool matched = i == commentString.length(); + shouldAddComment |= !matched; + } + } - if (isword && !skip) - return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex)); + if (shouldAddComment) + { + for (int currentLine : affectedLines) // order doesn't matter as changes are not multiline + { + Coordinates lineStart = { currentLine, 0 }; + Coordinates insertionEnd = lineStart; + InsertTextAt(insertionEnd, (commentString + ' ').c_str()); // sets insertion end + u.mOperations.push_back({ (commentString + ' ') , lineStart, insertionEnd, UndoOperationType::Add }); + Colorize(lineStart.mLine, 1); + } + } + else + { + for (int currentLine : affectedLines) // order doesn't matter as changes are not multiline + { + int currentIndex = 0; + while (currentIndex < mLines[currentLine].size() && (mLines[currentLine][currentIndex].mChar == ' ' || mLines[currentLine][currentIndex].mChar == '\t')) currentIndex++; + if (currentIndex == mLines[currentLine].size()) + continue; + int i = 0; + while (i < commentString.length() && currentIndex + i < mLines[currentLine].size() && mLines[currentLine][currentIndex + i].mChar == commentString[i]) i++; + bool matched = i == commentString.length(); + assert(matched); + if (currentIndex + i < mLines[currentLine].size() && mLines[currentLine][currentIndex + i].mChar == ' ') + i++; - if (!isword) - skip = false; + Coordinates start = { currentLine, GetCharacterColumn(currentLine, currentIndex) }; + Coordinates end = { currentLine, GetCharacterColumn(currentLine, currentIndex + i) }; + u.mOperations.push_back({ GetText(start, end) , start, end, UndoOperationType::Delete }); + DeleteRange(start, end); + Colorize(currentLine, 1); + } + } - cindex++; + u.mAfter = mState; + AddUndo(u); +} + +void TextEditor::RemoveCurrentLines() +{ + UndoRecord u; + u.mBefore = mState; + + if (AnyCursorHasSelection()) + { + for (int c = mState.mCurrentCursor; c > -1; c--) + { + if (!mState.mCursors[c].HasSelection()) + continue; + u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].GetSelectionStart(), mState.mCursors[c].GetSelectionEnd(), UndoOperationType::Delete }); + DeleteSelection(c); + } + } + MoveHome(); + OnCursorPositionChanged(); // might combine cursors + + for (int c = mState.mCurrentCursor; c > -1; c--) + { + int currentLine = mState.mCursors[c].mInteractiveEnd.mLine; + int nextLine = currentLine + 1; + int prevLine = currentLine - 1; + + Coordinates toDeleteStart, toDeleteEnd; + if (mLines.size() > nextLine) // next line exists + { + toDeleteStart = Coordinates(currentLine, 0); + toDeleteEnd = Coordinates(nextLine, 0); + SetCursorPosition({ mState.mCursors[c].mInteractiveEnd.mLine, 0 }, c); + } + else if (prevLine > -1) // previous line exists + { + toDeleteStart = Coordinates(prevLine, GetLineMaxColumn(prevLine)); + toDeleteEnd = Coordinates(currentLine, GetLineMaxColumn(currentLine)); + SetCursorPosition({ prevLine, 0 }, c); } else { - cindex = 0; - ++at.mLine; - skip = false; - isword = false; + toDeleteStart = Coordinates(currentLine, 0); + toDeleteEnd = Coordinates(currentLine, GetLineMaxColumn(currentLine)); + SetCursorPosition({ currentLine, 0 }, c); } + + u.mOperations.push_back({ GetText(toDeleteStart, toDeleteEnd), toDeleteStart, toDeleteEnd, UndoOperationType::Delete }); + + std::unordered_set handledCursors = { c }; + if (toDeleteStart.mLine != toDeleteEnd.mLine) + RemoveLine(currentLine, &handledCursors); + else + DeleteRange(toDeleteStart, toDeleteEnd); } - return at; + u.mAfter = mState; + AddUndo(u); } -int TextEditor::GetCharacterIndex(const Coordinates& aCoordinates) const +float TextEditor::TextDistanceToLineStart(const Coordinates& aFrom, bool aSanitizeCoords) const { - if (aCoordinates.mLine >= mLines.size()) + if (aSanitizeCoords) + return SanitizeCoordinates(aFrom).mColumn * mCharAdvance.x; + else + return aFrom.mColumn * mCharAdvance.x; +} + +void TextEditor::EnsureCursorVisible(int aCursor, bool aStartToo) +{ + if (aCursor == -1) + aCursor = mState.GetLastAddedCursorIndex(); + + mEnsureCursorVisible = aCursor; + mEnsureCursorVisibleStartToo = aStartToo; + return; +} + +TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates& aValue) const +{ + // Clamp in document and line limits + auto line = Max(aValue.mLine, 0); + auto column = Max(aValue.mColumn, 0); + Coordinates out; + if (line >= (int)mLines.size()) + { + if (mLines.empty()) + { + line = 0; + column = 0; + } + else + { + line = (int)mLines.size() - 1; + column = GetLineMaxColumn(line); + } + out = Coordinates(line, column); + } + else + { + column = mLines.empty() ? 0 : GetLineMaxColumn(line, column); + out = Coordinates(line, column); + } + + // Move if inside a tab character + int charIndex = GetCharacterIndexL(out); + if (charIndex > -1 && charIndex < mLines[out.mLine].size() && mLines[out.mLine][charIndex].mChar == '\t') + { + int columnToLeft = GetCharacterColumn(out.mLine, charIndex); + int columnToRight = GetCharacterColumn(out.mLine, GetCharacterIndexR(out)); + if (out.mColumn - columnToLeft <= columnToRight - out.mColumn) + out.mColumn = columnToLeft; + else + out.mColumn = columnToRight; + } + return out; +} + +TextEditor::Coordinates TextEditor::GetSanitizedCursorCoordinates(int aCursor, bool aStart) const +{ + aCursor = aCursor == -1 ? mState.mCurrentCursor : aCursor; + return SanitizeCoordinates(aStart ? mState.mCursors[aCursor].mInteractiveStart : mState.mCursors[aCursor].mInteractiveEnd); +} + +TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2& aPosition, bool* isOverLineNumber) const +{ + ImVec2 origin = ImGui::GetCursorScreenPos(); + ImVec2 local(aPosition.x - origin.x + 3.0f, aPosition.y - origin.y); + + if (isOverLineNumber != nullptr) + *isOverLineNumber = local.x < mTextStart; + + Coordinates out = { + Max(0, (int)floor(local.y / mCharAdvance.y)), + Max(0, (int)floor((local.x - mTextStart) / mCharAdvance.x)) + }; + out.mColumn = Max(0, (int)floor((local.x - mTextStart + POS_TO_COORDS_COLUMN_OFFSET * mCharAdvance.x) / mCharAdvance.x)); + + return SanitizeCoordinates(out); +} + +TextEditor::Coordinates TextEditor::FindWordStart(const Coordinates& aFrom) const +{ + if (aFrom.mLine >= (int)mLines.size()) + return aFrom; + + int lineIndex = aFrom.mLine; + auto& line = mLines[lineIndex]; + int charIndex = GetCharacterIndexL(aFrom); + + if (charIndex > (int)line.size() || line.size() == 0) + return aFrom; + if (charIndex == (int)line.size()) + charIndex--; + + bool initialIsWordChar = CharIsWordChar(line[charIndex].mChar); + bool initialIsSpace = isspace(line[charIndex].mChar); + char initialChar = line[charIndex].mChar; + while (Move(lineIndex, charIndex, true, true)) + { + bool isWordChar = CharIsWordChar(line[charIndex].mChar); + bool isSpace = isspace(line[charIndex].mChar); + if (initialIsSpace && !isSpace || + initialIsWordChar && !isWordChar || + !initialIsWordChar && !initialIsSpace && initialChar != line[charIndex].mChar) + { + Move(lineIndex, charIndex, false, true); // one step to the right + break; + } + } + return { aFrom.mLine, GetCharacterColumn(aFrom.mLine, charIndex) }; +} + +TextEditor::Coordinates TextEditor::FindWordEnd(const Coordinates& aFrom) const +{ + if (aFrom.mLine >= (int)mLines.size()) + return aFrom; + + int lineIndex = aFrom.mLine; + auto& line = mLines[lineIndex]; + auto charIndex = GetCharacterIndexL(aFrom); + + if (charIndex >= (int)line.size()) + return aFrom; + + bool initialIsWordChar = CharIsWordChar(line[charIndex].mChar); + bool initialIsSpace = isspace(line[charIndex].mChar); + char initialChar = line[charIndex].mChar; + while (Move(lineIndex, charIndex, false, true)) + { + if (charIndex == line.size()) + break; + bool isWordChar = CharIsWordChar(line[charIndex].mChar); + bool isSpace = isspace(line[charIndex].mChar); + if (initialIsSpace && !isSpace || + initialIsWordChar && !isWordChar || + !initialIsWordChar && !initialIsSpace && initialChar != line[charIndex].mChar) + break; + } + return { lineIndex, GetCharacterColumn(aFrom.mLine, charIndex) }; +} + +int TextEditor::GetCharacterIndexL(const Coordinates& aCoords) const +{ + if (aCoords.mLine >= mLines.size()) return -1; - auto& line = mLines[aCoordinates.mLine]; + + auto& line = mLines[aCoords.mLine]; int c = 0; int i = 0; - for (; i < line.size() && c < aCoordinates.mColumn;) + int tabCoordsLeft = 0; + + for (; i < line.size() && c < aCoords.mColumn;) { if (line[i].mChar == '\t') - c = (c / mTabSize) * mTabSize + mTabSize; + { + if (tabCoordsLeft == 0) + tabCoordsLeft = TabSizeAtColumn(c); + if (tabCoordsLeft > 0) + tabCoordsLeft--; + c++; + } else ++c; - i += UTF8CharLength(line[i].mChar); + if (tabCoordsLeft == 0) + i += UTF8CharLength(line[i].mChar); } return i; } +int TextEditor::GetCharacterIndexR(const Coordinates& aCoords) const +{ + if (aCoords.mLine >= mLines.size()) + return -1; + int c = 0; + int i = 0; + for (; i < mLines[aCoords.mLine].size() && c < aCoords.mColumn;) + MoveCharIndexAndColumn(aCoords.mLine, i, c); + return i; +} + int TextEditor::GetCharacterColumn(int aLine, int aIndex) const { if (aLine >= mLines.size()) return 0; - auto& line = mLines[aLine]; - int col = 0; - int i = 0; - while (i < aIndex && i < (int)line.size()) - { - auto c = line[i].mChar; - i += UTF8CharLength(c); - if (c == '\t') - col = (col / mTabSize) * mTabSize + mTabSize; - else - col++; - } - return col; -} - -int TextEditor::GetLineCharacterCount(int aLine) const -{ - if (aLine >= mLines.size()) - return 0; - auto& line = mLines[aLine]; int c = 0; - for (unsigned i = 0; i < line.size(); c++) - i += UTF8CharLength(line[i].mChar); + int i = 0; + while (i < aIndex && i < mLines[aLine].size()) + MoveCharIndexAndColumn(aLine, i, c); return c; } -int TextEditor::GetLineMaxColumn(int aLine) const +int TextEditor::GetFirstVisibleCharacterIndex(int aLine) const { if (aLine >= mLines.size()) return 0; - auto& line = mLines[aLine]; - int col = 0; - for (unsigned i = 0; i < line.size(); ) - { - auto c = line[i].mChar; - if (c == '\t') - col = (col / mTabSize) * mTabSize + mTabSize; - else - col++; - i += UTF8CharLength(c); - } - return col; + int c = 0; + int i = 0; + while (c < mFirstVisibleColumn && i < mLines[aLine].size()) + MoveCharIndexAndColumn(aLine, i, c); + if (c > mFirstVisibleColumn) + i--; + return i; } -bool TextEditor::IsOnWordBoundary(const Coordinates & aAt) const +int TextEditor::GetLineMaxColumn(int aLine, int aLimit) const { - if (aAt.mLine >= (int)mLines.size() || aAt.mColumn == 0) - return true; - - auto& line = mLines[aAt.mLine]; - auto cindex = GetCharacterIndex(aAt); - if (cindex >= (int)line.size()) - return true; - - if (mColorizerEnabled) - return line[cindex].mColorIndex != line[size_t(cindex - 1)].mColorIndex; - - return isspace(line[cindex].mChar) != isspace(line[cindex - 1].mChar); -} - -void TextEditor::RemoveLine(int aStart, int aEnd) -{ - assert(!mReadOnly); - assert(aEnd >= aStart); - assert(mLines.size() > (size_t)(aEnd - aStart)); - - ErrorMarkers etmp; - for (auto& i : mErrorMarkers) + if (aLine >= mLines.size()) + return 0; + int c = 0; + if (aLimit == -1) { - ErrorMarkers::value_type e(i.first >= aStart ? i.first - 1 : i.first, i.second); - if (e.first >= aStart && e.first <= aEnd) - continue; - etmp.insert(e); + for (int i = 0; i < mLines[aLine].size(); ) + MoveCharIndexAndColumn(aLine, i, c); } - mErrorMarkers = std::move(etmp); - - Breakpoints btmp; - for (auto i : mBreakpoints) + else { - if (i >= aStart && i <= aEnd) - continue; - btmp.insert(i >= aStart ? i - 1 : i); + for (int i = 0; i < mLines[aLine].size(); ) + { + MoveCharIndexAndColumn(aLine, i, c); + if (c > aLimit) + return aLimit; + } } - mBreakpoints = std::move(btmp); - - mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd); - assert(!mLines.empty()); - - mTextChanged = true; -} - -void TextEditor::RemoveLine(int aIndex) -{ - assert(!mReadOnly); - assert(mLines.size() > 1); - - ErrorMarkers etmp; - for (auto& i : mErrorMarkers) - { - ErrorMarkers::value_type e(i.first > aIndex ? i.first - 1 : i.first, i.second); - if (e.first - 1 == aIndex) - continue; - etmp.insert(e); - } - mErrorMarkers = std::move(etmp); - - Breakpoints btmp; - for (auto i : mBreakpoints) - { - if (i == aIndex) - continue; - btmp.insert(i >= aIndex ? i - 1 : i); - } - mBreakpoints = std::move(btmp); - - mLines.erase(mLines.begin() + aIndex); - assert(!mLines.empty()); - - mTextChanged = true; + return c; } TextEditor::Line& TextEditor::InsertLine(int aIndex) { assert(!mReadOnly); - auto& result = *mLines.insert(mLines.begin() + aIndex, Line()); - ErrorMarkers etmp; - for (auto& i : mErrorMarkers) - etmp.insert(ErrorMarkers::value_type(i.first >= aIndex ? i.first + 1 : i.first, i.second)); - mErrorMarkers = std::move(etmp); - - Breakpoints btmp; - for (auto i : mBreakpoints) - btmp.insert(i >= aIndex ? i + 1 : i); - mBreakpoints = std::move(btmp); + for (int c = 0; c <= mState.mCurrentCursor; c++) // handle multiple cursors + { + if (mState.mCursors[c].mInteractiveEnd.mLine >= aIndex) + SetCursorPosition({ mState.mCursors[c].mInteractiveEnd.mLine + 1, mState.mCursors[c].mInteractiveEnd.mColumn }, c); + } return result; } -std::string TextEditor::GetWordUnderCursor() const +void TextEditor::RemoveLine(int aIndex, const std::unordered_set* aHandledCursors) { - auto c = GetCursorPosition(); - return GetWordAt(c); + assert(!mReadOnly); + assert(mLines.size() > 1); + + mLines.erase(mLines.begin() + aIndex); + assert(!mLines.empty()); + + // handle multiple cursors + for (int c = 0; c <= mState.mCurrentCursor; c++) + { + if (mState.mCursors[c].mInteractiveEnd.mLine >= aIndex) + { + if (aHandledCursors == nullptr || aHandledCursors->find(c) == aHandledCursors->end()) // move up if has not been handled already + SetCursorPosition({ mState.mCursors[c].mInteractiveEnd.mLine - 1, mState.mCursors[c].mInteractiveEnd.mColumn }, c); + } + } } -std::string TextEditor::GetWordAt(const Coordinates & aCoords) const +void TextEditor::RemoveLines(int aStart, int aEnd) { - auto start = FindWordStart(aCoords); - auto end = FindWordEnd(aCoords); + assert(!mReadOnly); + assert(aEnd >= aStart); + assert(mLines.size() > (size_t)(aEnd - aStart)); - std::string r; + mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd); + assert(!mLines.empty()); - auto istart = GetCharacterIndex(start); - auto iend = GetCharacterIndex(end); - - for (auto it = istart; it < iend; ++it) - r.push_back(mLines[aCoords.mLine][it].mChar); - - return r; + // handle multiple cursors + for (int c = 0; c <= mState.mCurrentCursor; c++) + { + if (mState.mCursors[c].mInteractiveEnd.mLine >= aStart) + { + int targetLine = mState.mCursors[c].mInteractiveEnd.mLine - (aEnd - aStart); + targetLine = targetLine < 0 ? 0 : targetLine; + mState.mCursors[c].mInteractiveEnd.mLine = targetLine; + } + if (mState.mCursors[c].mInteractiveStart.mLine >= aStart) + { + int targetLine = mState.mCursors[c].mInteractiveStart.mLine - (aEnd - aStart); + targetLine = targetLine < 0 ? 0 : targetLine; + mState.mCursors[c].mInteractiveStart.mLine = targetLine; + } + } } -ImU32 TextEditor::GetGlyphColor(const Glyph & aGlyph) const +void TextEditor::DeleteRange(const Coordinates& aStart, const Coordinates& aEnd) { - if (!mColorizerEnabled) + assert(aEnd >= aStart); + assert(!mReadOnly); + + if (aEnd == aStart) + return; + + auto start = GetCharacterIndexL(aStart); + auto end = GetCharacterIndexR(aEnd); + + if (aStart.mLine == aEnd.mLine) + { + auto n = GetLineMaxColumn(aStart.mLine); + if (aEnd.mColumn >= n) + RemoveGlyphsFromLine(aStart.mLine, start); // from start to end of line + else + RemoveGlyphsFromLine(aStart.mLine, start, end); + } + else + { + RemoveGlyphsFromLine(aStart.mLine, start); // from start to end of line + RemoveGlyphsFromLine(aEnd.mLine, 0, end); + auto& firstLine = mLines[aStart.mLine]; + auto& lastLine = mLines[aEnd.mLine]; + + if (aStart.mLine < aEnd.mLine) + { + AddGlyphsToLine(aStart.mLine, firstLine.size(), lastLine.begin(), lastLine.end()); + for (int c = 0; c <= mState.mCurrentCursor; c++) // move up cursors in line that is being moved up + { + // if cursor is selecting the same range we are deleting, it's because this is being called from + // DeleteSelection which already sets the cursor position after the range is deleted + if (mState.mCursors[c].GetSelectionStart() == aStart && mState.mCursors[c].GetSelectionEnd() == aEnd) + continue; + if (mState.mCursors[c].mInteractiveEnd.mLine > aEnd.mLine) + break; + else if (mState.mCursors[c].mInteractiveEnd.mLine != aEnd.mLine) + continue; + int otherCursorEndCharIndex = GetCharacterIndexR(mState.mCursors[c].mInteractiveEnd); + int otherCursorStartCharIndex = GetCharacterIndexR(mState.mCursors[c].mInteractiveStart); + int otherCursorNewEndCharIndex = GetCharacterIndexR(aStart) + otherCursorEndCharIndex; + int otherCursorNewStartCharIndex = GetCharacterIndexR(aStart) + otherCursorStartCharIndex; + auto targetEndCoords = Coordinates(aStart.mLine, GetCharacterColumn(aStart.mLine, otherCursorNewEndCharIndex)); + auto targetStartCoords = Coordinates(aStart.mLine, GetCharacterColumn(aStart.mLine, otherCursorNewStartCharIndex)); + SetCursorPosition(targetStartCoords, c, true); + SetCursorPosition(targetEndCoords, c, false); + } + RemoveLines(aStart.mLine + 1, aEnd.mLine + 1); + } + } +} + +void TextEditor::DeleteSelection(int aCursor) +{ + if (aCursor == -1) + aCursor = mState.mCurrentCursor; + + if (mState.mCursors[aCursor].GetSelectionEnd() == mState.mCursors[aCursor].GetSelectionStart()) + return; + + Coordinates newCursorPos = mState.mCursors[aCursor].GetSelectionStart(); + DeleteRange(newCursorPos, mState.mCursors[aCursor].GetSelectionEnd()); + SetCursorPosition(newCursorPos, aCursor); + Colorize(newCursorPos.mLine, 1); +} + +void TextEditor::RemoveGlyphsFromLine(int aLine, int aStartChar, int aEndChar) +{ + int column = GetCharacterColumn(aLine, aStartChar); + auto& line = mLines[aLine]; + OnLineChanged(true, aLine, column, aEndChar - aStartChar, true); + line.erase(line.begin() + aStartChar, aEndChar == -1 ? line.end() : line.begin() + aEndChar); + OnLineChanged(false, aLine, column, aEndChar - aStartChar, true); +} + +void TextEditor::AddGlyphsToLine(int aLine, int aTargetIndex, Line::iterator aSourceStart, Line::iterator aSourceEnd) +{ + int targetColumn = GetCharacterColumn(aLine, aTargetIndex); + int charsInserted = std::distance(aSourceStart, aSourceEnd); + auto& line = mLines[aLine]; + OnLineChanged(true, aLine, targetColumn, charsInserted, false); + line.insert(line.begin() + aTargetIndex, aSourceStart, aSourceEnd); + OnLineChanged(false, aLine, targetColumn, charsInserted, false); +} + +void TextEditor::AddGlyphToLine(int aLine, int aTargetIndex, Glyph aGlyph) +{ + int targetColumn = GetCharacterColumn(aLine, aTargetIndex); + auto& line = mLines[aLine]; + OnLineChanged(true, aLine, targetColumn, 1, false); + line.insert(line.begin() + aTargetIndex, aGlyph); + OnLineChanged(false, aLine, targetColumn, 1, false); +} + +ImU32 TextEditor::GetGlyphColor(const Glyph& aGlyph) const +{ + if (mLanguageDefinition == nullptr) return mPalette[(int)PaletteIndex::Default]; if (aGlyph.mComment) return mPalette[(int)PaletteIndex::Comment]; @@ -695,74 +1975,96 @@ ImU32 TextEditor::GetGlyphColor(const Glyph & aGlyph) const return color; } -void TextEditor::HandleKeyboardInputs() +void TextEditor::HandleKeyboardInputs(bool aParentIsFocused) { - ImGuiIO& io = ImGui::GetIO(); - auto shift = io.KeyShift; - auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl; - auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt; - - if (ImGui::IsWindowFocused()) + if (ImGui::IsWindowFocused() || aParentIsFocused) { if (ImGui::IsWindowHovered()) ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput); //ImGui::CaptureKeyboardFromApp(true); + ImGuiIO& io = ImGui::GetIO(); + auto isOSX = io.ConfigMacOSXBehaviors; + auto alt = io.KeyAlt; + auto ctrl = io.KeyCtrl; + auto shift = io.KeyShift; + auto super = io.KeySuper; + + auto isShortcut = (isOSX ? (super && !ctrl) : (ctrl && !super)) && !alt && !shift; + auto isShiftShortcut = (isOSX ? (super && !ctrl) : (ctrl && !super)) && shift && !alt; + auto isWordmoveKey = isOSX ? alt : ctrl; + auto isAltOnly = alt && !ctrl && !shift && !super; + auto isCtrlOnly = ctrl && !alt && !shift && !super; + auto isShiftOnly = shift && !alt && !ctrl && !super; + io.WantCaptureKeyboard = true; io.WantTextInput = true; - if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Z))) + if (!mReadOnly && isShortcut && ImGui::IsKeyPressed(ImGuiKey_Z)) Undo(); - else if (!IsReadOnly() && !ctrl && !shift && alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace))) + else if (!mReadOnly && isAltOnly && ImGui::IsKeyPressed(ImGuiKey_Backspace)) Undo(); - else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Y))) + else if (!mReadOnly && isShortcut && ImGui::IsKeyPressed(ImGuiKey_Y)) Redo(); - else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow))) + else if (!mReadOnly && isShiftShortcut && ImGui::IsKeyPressed(ImGuiKey_Z)) + Redo(); + else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGuiKey_UpArrow)) MoveUp(1, shift); - else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow))) + else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGuiKey_DownArrow)) MoveDown(1, shift); - else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow))) - MoveLeft(1, shift, ctrl); - else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow))) - MoveRight(1, shift, ctrl); - else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageUp))) - MoveUp(GetPageSize() - 4, shift); - else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageDown))) - MoveDown(GetPageSize() - 4, shift); - else if (!alt && ctrl && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home))) + else if ((isOSX ? !ctrl : !alt) && !super && ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) + MoveLeft(shift, isWordmoveKey); + else if ((isOSX ? !ctrl : !alt) && !super && ImGui::IsKeyPressed(ImGuiKey_RightArrow)) + MoveRight(shift, isWordmoveKey); + else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGuiKey_PageUp)) + MoveUp(mVisibleLineCount - 2, shift); + else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGuiKey_PageDown)) + MoveDown(mVisibleLineCount - 2, shift); + else if (ctrl && !alt && !super && ImGui::IsKeyPressed(ImGuiKey_Home)) MoveTop(shift); - else if (ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End))) + else if (ctrl && !alt && !super && ImGui::IsKeyPressed(ImGuiKey_End)) MoveBottom(shift); - else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home))) + else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGuiKey_Home)) MoveHome(shift); - else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End))) + else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGuiKey_End)) MoveEnd(shift); - else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) - Delete(); - else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace))) - Backspace(); - else if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert))) - mOverwrite ^= true; - else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert))) + else if (!mReadOnly && !alt && !shift && !super && ImGui::IsKeyPressed(ImGuiKey_Delete)) + Delete(ctrl); + else if (!mReadOnly && !alt && !shift && !super && ImGui::IsKeyPressed(ImGuiKey_Backspace)) + Backspace(ctrl); + else if (!mReadOnly && !alt && ctrl && shift && !super && ImGui::IsKeyPressed(ImGuiKey_K)) + RemoveCurrentLines(); + else if (!mReadOnly && !alt && ctrl && !shift && !super && ImGui::IsKeyPressed(ImGuiKey_LeftBracket)) + ChangeCurrentLinesIndentation(false); + else if (!mReadOnly && !alt && ctrl && !shift && !super && ImGui::IsKeyPressed(ImGuiKey_RightBracket)) + ChangeCurrentLinesIndentation(true); + else if (!alt && ctrl && shift && !super && ImGui::IsKeyPressed(ImGuiKey_UpArrow)) + MoveUpCurrentLines(); + else if (!alt && ctrl && shift && !super && ImGui::IsKeyPressed(ImGuiKey_DownArrow)) + MoveDownCurrentLines(); + else if (!mReadOnly && !alt && ctrl && !shift && !super && ImGui::IsKeyPressed(ImGuiKey_Slash)) + ToggleLineComment(); + else if (isCtrlOnly && ImGui::IsKeyPressed(ImGuiKey_Insert)) Copy(); - else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C))) + else if (isShortcut && ImGui::IsKeyPressed(ImGuiKey_C)) Copy(); - else if (!IsReadOnly() && !ctrl && shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert))) + else if (!mReadOnly && isShiftOnly && ImGui::IsKeyPressed(ImGuiKey_Insert)) Paste(); - else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_V))) + else if (!mReadOnly && isShortcut && ImGui::IsKeyPressed(ImGuiKey_V)) Paste(); - else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_X))) + else if (isShortcut && ImGui::IsKeyPressed(ImGuiKey_X)) Cut(); - else if (!ctrl && shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) + else if (isShiftOnly && ImGui::IsKeyPressed(ImGuiKey_Delete)) Cut(); - else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_A))) + else if (isShortcut && ImGui::IsKeyPressed(ImGuiKey_A)) SelectAll(); - else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter))) + else if (isShortcut && ImGui::IsKeyPressed(ImGuiKey_D)) + AddCursorForNextOccurrence(); + else if (!mReadOnly && !alt && !ctrl && !shift && !super && (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter))) EnterCharacter('\n', false); - else if (!IsReadOnly() && !ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Tab))) + else if (!mReadOnly && !alt && !ctrl && !super && ImGui::IsKeyPressed(ImGuiKey_Tab)) EnterCharacter('\t', shift); - - if (!IsReadOnly() && !io.InputQueueCharacters.empty()) + if (!mReadOnly && !io.InputQueueCharacters.empty() && ctrl == alt && !super) { for (int i = 0; i < io.InputQueueCharacters.Size; i++) { @@ -782,14 +2084,55 @@ void TextEditor::HandleMouseInputs() auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl; auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt; + /* + Pan with middle mouse button + */ + mPanning &= ImGui::IsMouseDown(2); + if (mPanning && ImGui::IsMouseDragging(2)) + { + ImVec2 scroll = { ImGui::GetScrollX(), ImGui::GetScrollY() }; + ImVec2 currentMousePos = ImGui::GetMouseDragDelta(2); + ImVec2 mouseDelta = { + currentMousePos.x - mLastMousePos.x, + currentMousePos.y - mLastMousePos.y + }; + ImGui::SetScrollY(scroll.y - mouseDelta.y); + ImGui::SetScrollX(scroll.x - mouseDelta.x); + mLastMousePos = currentMousePos; + } + + // Mouse left button dragging (=> update selection) + mDraggingSelection &= ImGui::IsMouseDown(0); + if (mDraggingSelection && ImGui::IsMouseDragging(0)) + { + io.WantCaptureMouse = true; + Coordinates cursorCoords = ScreenPosToCoordinates(ImGui::GetMousePos()); + SetCursorPosition(cursorCoords, mState.GetLastAddedCursorIndex(), false); + } + if (ImGui::IsWindowHovered()) { + auto click = ImGui::IsMouseClicked(0); if (!shift && !alt) { - auto click = ImGui::IsMouseClicked(0); auto doubleClick = ImGui::IsMouseDoubleClicked(0); auto t = ImGui::GetTime(); - auto tripleClick = click && !doubleClick && (mLastClick != -1.0f && (t - mLastClick) < io.MouseDoubleClickTime); + auto tripleClick = click && !doubleClick && + (mLastClickTime != -1.0f && (t - mLastClickTime) < io.MouseDoubleClickTime && + Distance(io.MousePos, mLastClickPos) < 0.01f); + + if (click) + mDraggingSelection = true; + + /* + Pan with middle mouse button + */ + + if (ImGui::IsMouseClicked(2)) + { + mPanning = true; + mLastMousePos = ImGui::GetMouseDragDelta(2); + } /* Left mouse button triple click @@ -797,14 +2140,18 @@ void TextEditor::HandleMouseInputs() if (tripleClick) { - if (!ctrl) - { - mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos()); - mSelectionMode = SelectionMode::Line; - SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); - } + if (ctrl) + mState.AddCursor(); + else + mState.mCurrentCursor = 0; - mLastClick = -1.0f; + Coordinates cursorCoords = ScreenPosToCoordinates(ImGui::GetMousePos()); + Coordinates targetCursorPos = cursorCoords.mLine < mLines.size() - 1 ? + Coordinates{ cursorCoords.mLine + 1, 0 } : + Coordinates{ cursorCoords.mLine, GetLineMaxColumn(cursorCoords.mLine) }; + SetSelection({ cursorCoords.mLine, 0 }, targetCursorPos, mState.mCurrentCursor); + + mLastClickTime = -1.0f; } /* @@ -813,17 +2160,16 @@ void TextEditor::HandleMouseInputs() else if (doubleClick) { - if (!ctrl) - { - mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos()); - if (mSelectionMode == SelectionMode::Line) - mSelectionMode = SelectionMode::Normal; - else - mSelectionMode = SelectionMode::Word; - SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); - } + if (ctrl) + mState.AddCursor(); + else + mState.mCurrentCursor = 0; - mLastClick = (float)ImGui::GetTime(); + Coordinates cursorCoords = ScreenPosToCoordinates(ImGui::GetMousePos()); + SetSelection(FindWordStart(cursorCoords), FindWordEnd(cursorCoords), mState.mCurrentCursor); + + mLastClickTime = (float)ImGui::GetTime(); + mLastClickPos = io.MousePos; } /* @@ -831,222 +2177,198 @@ void TextEditor::HandleMouseInputs() */ else if (click) { - mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos()); if (ctrl) - mSelectionMode = SelectionMode::Word; + mState.AddCursor(); else - mSelectionMode = SelectionMode::Normal; - SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); + mState.mCurrentCursor = 0; - mLastClick = (float)ImGui::GetTime(); + bool isOverLineNumber; + Coordinates cursorCoords = ScreenPosToCoordinates(ImGui::GetMousePos(), &isOverLineNumber); + if (isOverLineNumber) + { + Coordinates targetCursorPos = cursorCoords.mLine < mLines.size() - 1 ? + Coordinates{ cursorCoords.mLine + 1, 0 } : + Coordinates{ cursorCoords.mLine, GetLineMaxColumn(cursorCoords.mLine) }; + SetSelection({ cursorCoords.mLine, 0 }, targetCursorPos, mState.mCurrentCursor); + } + else + SetCursorPosition(cursorCoords, mState.GetLastAddedCursorIndex()); + + mLastClickTime = (float)ImGui::GetTime(); + mLastClickPos = io.MousePos; } - // Mouse left button dragging (=> update selection) - else if (ImGui::IsMouseDragging(0) && ImGui::IsMouseDown(0)) + else if (ImGui::IsMouseReleased(0)) { - io.WantCaptureMouse = true; - mState.mCursorPosition = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos()); - SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); + mState.SortCursorsFromTopToBottom(); + MergeCursorsIfPossible(); + } + } + else if (shift) + { + if (click) + { + Coordinates newSelection = ScreenPosToCoordinates(ImGui::GetMousePos()); + SetCursorPosition(newSelection, mState.mCurrentCursor, false); } } } } -void TextEditor::Render() +void TextEditor::UpdateViewVariables(float aScrollX, float aScrollY) +{ + mContentHeight = ImGui::GetWindowHeight() - (IsHorizontalScrollbarVisible() ? IMGUI_SCROLLBAR_WIDTH : 0.0f); + mContentWidth = ImGui::GetWindowWidth() - (IsVerticalScrollbarVisible() ? IMGUI_SCROLLBAR_WIDTH : 0.0f); + + mVisibleLineCount = Max((int)ceil(mContentHeight / mCharAdvance.y), 0); + mFirstVisibleLine = Max((int)(aScrollY / mCharAdvance.y), 0); + mLastVisibleLine = Max((int)((mContentHeight + aScrollY) / mCharAdvance.y), 0); + + mVisibleColumnCount = Max((int)ceil((mContentWidth - Max(mTextStart - aScrollX, 0.0f)) / mCharAdvance.x), 0); + mFirstVisibleColumn = Max((int)(Max(aScrollX - mTextStart, 0.0f) / mCharAdvance.x), 0); + mLastVisibleColumn = Max((int)((mContentWidth + aScrollX - mTextStart) / mCharAdvance.x), 0); +} + +void TextEditor::Render(bool aParentIsFocused) { /* Compute mCharAdvance regarding to scaled font size (Ctrl + mouse wheel)*/ - const float fontSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, "#", nullptr, nullptr).x; - mCharAdvance = ImVec2(fontSize, ImGui::GetTextLineHeightWithSpacing() * mLineSpacing); + const float fontWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, "#", nullptr, nullptr).x; + const float fontHeight = ImGui::GetTextLineHeightWithSpacing(); + mCharAdvance = ImVec2(fontWidth, fontHeight * mLineSpacing); - /* Update palette with the current alpha from style */ - for (int i = 0; i < (int)PaletteIndex::Max; ++i) + // Deduce mTextStart by evaluating mLines size (global lineMax) plus two spaces as text width + mTextStart = mLeftMargin; + static char lineNumberBuffer[16]; + if (mShowLineNumbers) { - auto color = ImGui::ColorConvertU32ToFloat4(mPaletteBase[i]); - color.w *= ImGui::GetStyle().Alpha; - mPalette[i] = ImGui::ColorConvertFloat4ToU32(color); - } - - assert(mLineBuffer.empty()); - - auto contentSize = ImGui::GetWindowContentRegionMax(); - auto drawList = ImGui::GetWindowDrawList(); - float longest(mTextStart); - - if (mScrollToTop) - { - mScrollToTop = false; - ImGui::SetScrollY(0.f); + snprintf(lineNumberBuffer, 16, " %zu ", mLines.size()); + mTextStart += ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, lineNumberBuffer, nullptr, nullptr).x; } ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos(); - auto scrollX = ImGui::GetScrollX(); - auto scrollY = ImGui::GetScrollY(); - - auto lineNo = (int)floor(scrollY / mCharAdvance.y); - auto globalLineMax = (int)mLines.size(); - auto lineMax = std::max(0, std::min((int)mLines.size() - 1, lineNo + (int)floor((scrollY + contentSize.y) / mCharAdvance.y))); - - // Deduce mTextStart by evaluating mLines size (global lineMax) plus two spaces as text width - char buf[16]; - snprintf(buf, 16, " %d ", globalLineMax); - mTextStart = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr).x + mLeftMargin; + mScrollX = ImGui::GetScrollX(); + mScrollY = ImGui::GetScrollY(); + UpdateViewVariables(mScrollX, mScrollY); + int maxColumnLimited = 0; if (!mLines.empty()) { + auto drawList = ImGui::GetWindowDrawList(); float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x; - while (lineNo <= lineMax) + for (int lineNo = mFirstVisibleLine; lineNo <= mLastVisibleLine && lineNo < mLines.size(); lineNo++) { ImVec2 lineStartScreenPos = ImVec2(cursorScreenPos.x, cursorScreenPos.y + lineNo * mCharAdvance.y); ImVec2 textScreenPos = ImVec2(lineStartScreenPos.x + mTextStart, lineStartScreenPos.y); auto& line = mLines[lineNo]; - longest = std::max(mTextStart + TextDistanceToLineStart(Coordinates(lineNo, GetLineMaxColumn(lineNo))), longest); - auto columnNo = 0; + maxColumnLimited = Max(GetLineMaxColumn(lineNo, mLastVisibleColumn), maxColumnLimited); + Coordinates lineStartCoord(lineNo, 0); - Coordinates lineEndCoord(lineNo, GetLineMaxColumn(lineNo)); + Coordinates lineEndCoord(lineNo, maxColumnLimited); // Draw selection for the current line - float sstart = -1.0f; - float ssend = -1.0f; - - assert(mState.mSelectionStart <= mState.mSelectionEnd); - if (mState.mSelectionStart <= lineEndCoord) - sstart = mState.mSelectionStart > lineStartCoord ? TextDistanceToLineStart(mState.mSelectionStart) : 0.0f; - if (mState.mSelectionEnd > lineStartCoord) - ssend = TextDistanceToLineStart(mState.mSelectionEnd < lineEndCoord ? mState.mSelectionEnd : lineEndCoord); - - if (mState.mSelectionEnd.mLine > lineNo) - ssend += mCharAdvance.x; - - if (sstart != -1 && ssend != -1 && sstart < ssend) + for (int c = 0; c <= mState.mCurrentCursor; c++) { - ImVec2 vstart(lineStartScreenPos.x + mTextStart + sstart, lineStartScreenPos.y); - ImVec2 vend(lineStartScreenPos.x + mTextStart + ssend, lineStartScreenPos.y + mCharAdvance.y); - drawList->AddRectFilled(vstart, vend, mPalette[(int)PaletteIndex::Selection]); - } + float rectStart = -1.0f; + float rectEnd = -1.0f; + Coordinates cursorSelectionStart = mState.mCursors[c].GetSelectionStart(); + Coordinates cursorSelectionEnd = mState.mCursors[c].GetSelectionEnd(); + assert(cursorSelectionStart <= cursorSelectionEnd); - // Draw breakpoints - auto start = ImVec2(lineStartScreenPos.x + scrollX, lineStartScreenPos.y); + if (cursorSelectionStart <= lineEndCoord) + rectStart = cursorSelectionStart > lineStartCoord ? TextDistanceToLineStart(cursorSelectionStart) : 0.0f; + if (cursorSelectionEnd > lineStartCoord) + rectEnd = TextDistanceToLineStart(cursorSelectionEnd < lineEndCoord ? cursorSelectionEnd : lineEndCoord); + if (cursorSelectionEnd.mLine > lineNo || cursorSelectionEnd.mLine == lineNo && cursorSelectionEnd > lineEndCoord) + rectEnd += mCharAdvance.x; - if (mBreakpoints.count(lineNo + 1) != 0) - { - auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y); - drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::Breakpoint]); - } - - // Draw error markers - auto errorIt = mErrorMarkers.find(lineNo + 1); - if (errorIt != mErrorMarkers.end()) - { - auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y); - drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::ErrorMarker]); - - if (ImGui::IsMouseHoveringRect(lineStartScreenPos, end)) - { - ImGui::BeginTooltip(); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.2f, 0.2f, 1.0f)); - ImGui::Text("Error at line %d:", errorIt->first); - ImGui::PopStyleColor(); - ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 0.2f, 1.0f)); - ImGui::Text("%s", errorIt->second.c_str()); - ImGui::PopStyleColor(); - ImGui::EndTooltip(); - } + if (rectStart != -1 && rectEnd != -1 && rectStart < rectEnd) + drawList->AddRectFilled( + ImVec2{ lineStartScreenPos.x + mTextStart + rectStart, lineStartScreenPos.y }, + ImVec2{ lineStartScreenPos.x + mTextStart + rectEnd, lineStartScreenPos.y + mCharAdvance.y }, + mPalette[(int)PaletteIndex::Selection]); } // Draw line number (right aligned) - snprintf(buf, 16, "%d ", lineNo + 1); - - auto lineNoWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr).x; - drawList->AddText(ImVec2(lineStartScreenPos.x + mTextStart - lineNoWidth, lineStartScreenPos.y), mPalette[(int)PaletteIndex::LineNumber], buf); - - if (mState.mCursorPosition.mLine == lineNo) + if (mShowLineNumbers) { - auto focused = ImGui::IsWindowFocused(); + snprintf(lineNumberBuffer, 16, "%d ", lineNo + 1); + float lineNoWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, lineNumberBuffer, nullptr, nullptr).x; + drawList->AddText(ImVec2(lineStartScreenPos.x + mTextStart - lineNoWidth, lineStartScreenPos.y), mPalette[(int)PaletteIndex::LineNumber], lineNumberBuffer); + } - // Highlight the current line (where the cursor is) - if (!HasSelection()) - { - auto end = ImVec2(start.x + contentSize.x + scrollX, start.y + mCharAdvance.y); - drawList->AddRectFilled(start, end, mPalette[(int)(focused ? PaletteIndex::CurrentLineFill : PaletteIndex::CurrentLineFillInactive)]); - drawList->AddRect(start, end, mPalette[(int)PaletteIndex::CurrentLineEdge], 1.0f); - } + std::vector cursorCoordsInThisLine; + for (int c = 0; c <= mState.mCurrentCursor; c++) + { + if (mState.mCursors[c].mInteractiveEnd.mLine == lineNo) + cursorCoordsInThisLine.push_back(mState.mCursors[c].mInteractiveEnd); + } + if (cursorCoordsInThisLine.size() > 0) + { + bool focused = ImGui::IsWindowFocused() || aParentIsFocused; - // Render the cursor + // Render the cursors if (focused) { - auto timeEnd = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - auto elapsed = timeEnd - mStartTime; - if (elapsed > 400) + for (const auto& cursorCoords : cursorCoordsInThisLine) { float width = 1.0f; - auto cindex = GetCharacterIndex(mState.mCursorPosition); - float cx = TextDistanceToLineStart(mState.mCursorPosition); + auto cindex = GetCharacterIndexR(cursorCoords); + float cx = TextDistanceToLineStart(cursorCoords); - if (mOverwrite && cindex < (int)line.size()) - { - auto c = line[cindex].mChar; - if (c == '\t') - { - auto x = (1.0f + std::floor((1.0f + cx) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); - width = x - cx; - } - else - { - char buf2[2]; - buf2[0] = line[cindex].mChar; - buf2[1] = '\0'; - width = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf2).x; - } - } ImVec2 cstart(textScreenPos.x + cx, lineStartScreenPos.y); ImVec2 cend(textScreenPos.x + cx + width, lineStartScreenPos.y + mCharAdvance.y); drawList->AddRectFilled(cstart, cend, mPalette[(int)PaletteIndex::Cursor]); - if (elapsed > 800) - mStartTime = timeEnd; + if (mCursorOnBracket) + { + ImVec2 topLeft = { cstart.x, lineStartScreenPos.y + fontHeight + 1.0f }; + ImVec2 bottomRight = { topLeft.x + mCharAdvance.x, topLeft.y + 1.0f }; + drawList->AddRectFilled(topLeft, bottomRight, mPalette[(int)PaletteIndex::Cursor]); + } } } } // Render colorized text - auto prevColor = line.empty() ? mPalette[(int)PaletteIndex::Default] : GetGlyphColor(line[0]); - ImVec2 bufferOffset; - - for (int i = 0; i < line.size();) + static std::string glyphBuffer; + int charIndex = GetFirstVisibleCharacterIndex(lineNo); + int column = mFirstVisibleColumn; // can be in the middle of tab character + while (charIndex < mLines[lineNo].size() && column <= mLastVisibleColumn) { - auto& glyph = line[i]; + auto& glyph = line[charIndex]; auto color = GetGlyphColor(glyph); - - if ((color != prevColor || glyph.mChar == '\t' || glyph.mChar == ' ') && !mLineBuffer.empty()) - { - const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, textScreenPos.y + bufferOffset.y); - drawList->AddText(newOffset, prevColor, mLineBuffer.c_str()); - auto textSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, mLineBuffer.c_str(), nullptr, nullptr); - bufferOffset.x += textSize.x; - mLineBuffer.clear(); - } - prevColor = color; + ImVec2 targetGlyphPos = { lineStartScreenPos.x + mTextStart + TextDistanceToLineStart({lineNo, column}, false), lineStartScreenPos.y }; if (glyph.mChar == '\t') { - auto oldX = bufferOffset.x; - bufferOffset.x = (1.0f + std::floor((1.0f + bufferOffset.x) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); - ++i; - if (mShowWhitespaces) { + ImVec2 p1, p2, p3, p4; + const auto s = ImGui::GetFontSize(); - const auto x1 = textScreenPos.x + oldX + 1.0f; - const auto x2 = textScreenPos.x + bufferOffset.x - 1.0f; - const auto y = textScreenPos.y + bufferOffset.y + s * 0.5f; - const ImVec2 p1(x1, y); - const ImVec2 p2(x2, y); - const ImVec2 p3(x2 - s * 0.2f, y - s * 0.2f); - const ImVec2 p4(x2 - s * 0.2f, y + s * 0.2f); - drawList->AddLine(p1, p2, 0x90909090); - drawList->AddLine(p2, p3, 0x90909090); - drawList->AddLine(p2, p4, 0x90909090); + const auto x1 = targetGlyphPos.x + mCharAdvance.x * 0.3f; + const auto y = targetGlyphPos.y + fontHeight * 0.5f; + + if (mShortTabs) + { + const auto x2 = targetGlyphPos.x + mCharAdvance.x; + p1 = ImVec2(x1, y); + p2 = ImVec2(x2, y); + p3 = ImVec2(x2 - s * 0.16f, y - s * 0.16f); + p4 = ImVec2(x2 - s * 0.16f, y + s * 0.16f); + } + else + { + const auto x2 = targetGlyphPos.x + TabSizeAtColumn(column) * mCharAdvance.x - mCharAdvance.x * 0.3f; + p1 = ImVec2(x1, y); + p2 = ImVec2(x2, y); + p3 = ImVec2(x2 - s * 0.2f, y - s * 0.2f); + p4 = ImVec2(x2 - s * 0.2f, y + s * 0.2f); + } + + drawList->AddLine(p1, p2, mPalette[(int)PaletteIndex::ControlCharacter]); + drawList->AddLine(p2, p3, mPalette[(int)PaletteIndex::ControlCharacter]); + drawList->AddLine(p2, p4, mPalette[(int)PaletteIndex::ControlCharacter]); } } else if (glyph.mChar == ' ') @@ -1054,1083 +2376,190 @@ void TextEditor::Render() if (mShowWhitespaces) { const auto s = ImGui::GetFontSize(); - const auto x = textScreenPos.x + bufferOffset.x + spaceSize * 0.5f; - const auto y = textScreenPos.y + bufferOffset.y + s * 0.5f; - drawList->AddCircleFilled(ImVec2(x, y), 1.5f, 0x80808080, 4); + const auto x = targetGlyphPos.x + spaceSize * 0.5f; + const auto y = targetGlyphPos.y + s * 0.5f; + drawList->AddCircleFilled(ImVec2(x, y), 1.5f, mPalette[(int)PaletteIndex::ControlCharacter], 4); } - bufferOffset.x += spaceSize; - i++; } else { - auto l = UTF8CharLength(glyph.mChar); - while (l-- > 0) - mLineBuffer.push_back(line[i++].mChar); - } - ++columnNo; - } - - if (!mLineBuffer.empty()) - { - const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, textScreenPos.y + bufferOffset.y); - drawList->AddText(newOffset, prevColor, mLineBuffer.c_str()); - mLineBuffer.clear(); - } - - ++lineNo; - } - - // Draw a tooltip on known identifiers/preprocessor symbols - if (ImGui::IsMousePosValid()) - { - auto id = GetWordAt(ScreenPosToCoordinates(ImGui::GetMousePos())); - if (!id.empty()) - { - auto it = mLanguageDefinition.mIdentifiers.find(id); - if (it != mLanguageDefinition.mIdentifiers.end()) - { - ImGui::BeginTooltip(); - ImGui::TextUnformatted(it->second.mDeclaration.c_str()); - ImGui::EndTooltip(); - } - else - { - auto pi = mLanguageDefinition.mPreprocIdentifiers.find(id); - if (pi != mLanguageDefinition.mPreprocIdentifiers.end()) + int seqLength = UTF8CharLength(glyph.mChar); + if (mCursorOnBracket && seqLength == 1 && mMatchingBracketCoords == Coordinates{ lineNo, column }) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted(pi->second.mDeclaration.c_str()); - ImGui::EndTooltip(); + ImVec2 topLeft = { targetGlyphPos.x, targetGlyphPos.y + fontHeight + 1.0f }; + ImVec2 bottomRight = { topLeft.x + mCharAdvance.x, topLeft.y + 1.0f }; + drawList->AddRectFilled(topLeft, bottomRight, mPalette[(int)PaletteIndex::Cursor]); } + glyphBuffer.clear(); + for (int i = 0; i < seqLength; i++) + glyphBuffer.push_back(line[charIndex + i].mChar); + drawList->AddText(targetGlyphPos, color, glyphBuffer.c_str()); } + + MoveCharIndexAndColumn(lineNo, charIndex, column); } } } + mCurrentSpaceHeight = (mLines.size() + Min(mVisibleLineCount - 1, (int)mLines.size())) * mCharAdvance.y; + mCurrentSpaceWidth = Max((maxColumnLimited + Min(mVisibleColumnCount - 1, maxColumnLimited)) * mCharAdvance.x, mCurrentSpaceWidth); + ImGui::SetCursorPos(ImVec2(0, 0)); + ImGui::Dummy(ImVec2(mCurrentSpaceWidth, mCurrentSpaceHeight)); - ImGui::Dummy(ImVec2((longest + 2), mLines.size() * mCharAdvance.y)); - - if (mScrollToCursor) + if (mEnsureCursorVisible > -1) { - EnsureCursorVisible(); - ImGui::SetWindowFocus(); - mScrollToCursor = false; - } -} - -void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder) -{ - mWithinRender = true; - mTextChanged = false; - mCursorPositionChanged = false; - - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(mPalette[(int)PaletteIndex::Background])); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); - if (!mIgnoreImGuiChild) - ImGui::BeginChild(aTitle, aSize, aBorder, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NoMove); - - if (mHandleKeyboardInputs) - { - HandleKeyboardInputs(); - ImGui::PushAllowKeyboardFocus(true); - } - - if (mHandleMouseInputs) - HandleMouseInputs(); - - ColorizeInternal(); - Render(); - - if (mHandleKeyboardInputs) - ImGui::PopAllowKeyboardFocus(); - - if (!mIgnoreImGuiChild) - ImGui::EndChild(); - - ImGui::PopStyleVar(); - ImGui::PopStyleColor(); - - mWithinRender = false; -} - -void TextEditor::SetText(const std::string & aText) -{ - mLines.clear(); - mLines.emplace_back(Line()); - for (auto chr : aText) - { - if (chr == '\r') + for (int i = 0; i < (mEnsureCursorVisibleStartToo ? 2 : 1); i++) // first pass for interactive end and second pass for interactive start { - // ignore the carriage return character - } - else if (chr == '\n') - mLines.emplace_back(Line()); - else - { - mLines.back().emplace_back(Glyph(chr, PaletteIndex::Default)); + if (i) UpdateViewVariables(mScrollX, mScrollY); // second pass depends on changes made in first pass + Coordinates targetCoords = GetSanitizedCursorCoordinates(mEnsureCursorVisible, i); // cursor selection end or start + if (targetCoords.mLine <= mFirstVisibleLine) + { + float targetScroll = std::max(0.0f, (targetCoords.mLine - 0.5f) * mCharAdvance.y); + if (targetScroll < mScrollY) + ImGui::SetScrollY(targetScroll); + } + if (targetCoords.mLine >= mLastVisibleLine) + { + float targetScroll = std::max(0.0f, (targetCoords.mLine + 1.5f) * mCharAdvance.y - mContentHeight); + if (targetScroll > mScrollY) + ImGui::SetScrollY(targetScroll); + } + if (targetCoords.mColumn <= mFirstVisibleColumn) + { + float targetScroll = std::max(0.0f, mTextStart + (targetCoords.mColumn - 0.5f) * mCharAdvance.x); + if (targetScroll < mScrollX) + ImGui::SetScrollX(mScrollX = targetScroll); + } + if (targetCoords.mColumn >= mLastVisibleColumn) + { + float targetScroll = std::max(0.0f, mTextStart + (targetCoords.mColumn + 0.5f) * mCharAdvance.x - mContentWidth); + if (targetScroll > mScrollX) + ImGui::SetScrollX(mScrollX = targetScroll); + } } + mEnsureCursorVisible = -1; + } + if (mScrollToTop) + { + ImGui::SetScrollY(0.0f); + mScrollToTop = false; + } + if (mSetViewAtLine > -1) + { + float targetScroll; + switch (mSetViewAtLineMode) + { + default: + case SetViewAtLineMode::FirstVisibleLine: + targetScroll = std::max(0.0f, (float)mSetViewAtLine * mCharAdvance.y); + break; + case SetViewAtLineMode::LastVisibleLine: + targetScroll = std::max(0.0f, (float)(mSetViewAtLine - (mLastVisibleLine - mFirstVisibleLine)) * mCharAdvance.y); + break; + case SetViewAtLineMode::Centered: + targetScroll = std::max(0.0f, ((float)mSetViewAtLine - (float)(mLastVisibleLine - mFirstVisibleLine) * 0.5f) * mCharAdvance.y); + break; + } + ImGui::SetScrollY(targetScroll); + mSetViewAtLine = -1; } - - mTextChanged = true; - mScrollToTop = true; - - mUndoBuffer.clear(); - mUndoIndex = 0; - - Colorize(); } -void TextEditor::SetTextLines(const std::vector & aLines) +void TextEditor::OnCursorPositionChanged() { - mLines.clear(); + if (mState.mCurrentCursor == 0 && !mState.mCursors[0].HasSelection()) // only one cursor without selection + mCursorOnBracket = FindMatchingBracket(mState.mCursors[0].mInteractiveEnd.mLine, + GetCharacterIndexR(mState.mCursors[0].mInteractiveEnd), mMatchingBracketCoords); + else + mCursorOnBracket = false; - if (aLines.empty()) + if (!mDraggingSelection) { - mLines.emplace_back(Line()); + mState.SortCursorsFromTopToBottom(); + MergeCursorsIfPossible(); + } +} + +void TextEditor::OnLineChanged(bool aBeforeChange, int aLine, int aColumn, int aCharCount, bool aDeleted) // adjusts cursor position when other cursor writes/deletes in the same line +{ + static std::unordered_map cursorCharIndices; + if (aBeforeChange) + { + cursorCharIndices.clear(); + for (int c = 0; c <= mState.mCurrentCursor; c++) + { + if (mState.mCursors[c].mInteractiveEnd.mLine == aLine && // cursor is at the line + mState.mCursors[c].mInteractiveEnd.mColumn > aColumn && // cursor is to the right of changing part + mState.mCursors[c].GetSelectionEnd() == mState.mCursors[c].GetSelectionStart()) // cursor does not have a selection + { + cursorCharIndices[c] = GetCharacterIndexR({ aLine, mState.mCursors[c].mInteractiveEnd.mColumn }); + cursorCharIndices[c] += aDeleted ? -aCharCount : aCharCount; + } + } } else { - mLines.resize(aLines.size()); - - for (size_t i = 0; i < aLines.size(); ++i) - { - const std::string & aLine = aLines[i]; - - mLines[i].reserve(aLine.size()); - for (size_t j = 0; j < aLine.size(); ++j) - mLines[i].emplace_back(Glyph(aLine[j], PaletteIndex::Default)); - } + for (auto& item : cursorCharIndices) + SetCursorPosition({ aLine, GetCharacterColumn(aLine, item.second) }, item.first); } - - mTextChanged = true; - mScrollToTop = true; - - mUndoBuffer.clear(); - mUndoIndex = 0; - - Colorize(); } -void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) +void TextEditor::MergeCursorsIfPossible() +{ + // requires the cursors to be sorted from top to bottom + std::unordered_set cursorsToDelete; + if (AnyCursorHasSelection()) + { + // merge cursors if they overlap + for (int c = mState.mCurrentCursor; c > 0; c--)// iterate backwards through pairs + { + int pc = c - 1; // pc for previous cursor + + bool pcContainsC = mState.mCursors[pc].GetSelectionEnd() >= mState.mCursors[c].GetSelectionEnd(); + bool pcContainsStartOfC = mState.mCursors[pc].GetSelectionEnd() > mState.mCursors[c].GetSelectionStart(); + + if (pcContainsC) + { + cursorsToDelete.insert(c); + } + else if (pcContainsStartOfC) + { + Coordinates pcStart = mState.mCursors[pc].GetSelectionStart(); + Coordinates cEnd = mState.mCursors[c].GetSelectionEnd(); + mState.mCursors[pc].mInteractiveEnd = cEnd; + mState.mCursors[pc].mInteractiveStart = pcStart; + cursorsToDelete.insert(c); + } + } + } + else + { + // merge cursors if they are at the same position + for (int c = mState.mCurrentCursor; c > 0; c--)// iterate backwards through pairs + { + int pc = c - 1; + if (mState.mCursors[pc].mInteractiveEnd == mState.mCursors[c].mInteractiveEnd) + cursorsToDelete.insert(c); + } + } + for (int c = mState.mCurrentCursor; c > -1; c--)// iterate backwards through each of them + { + if (cursorsToDelete.find(c) != cursorsToDelete.end()) + mState.mCursors.erase(mState.mCursors.begin() + c); + } + mState.mCurrentCursor -= cursorsToDelete.size(); +} + +void TextEditor::AddUndo(UndoRecord& aValue) { assert(!mReadOnly); - - UndoRecord u; - - u.mBefore = mState; - - if (HasSelection()) - { - if (aChar == '\t' && mState.mSelectionStart.mLine != mState.mSelectionEnd.mLine) - { - - auto start = mState.mSelectionStart; - auto end = mState.mSelectionEnd; - auto originalEnd = end; - - if (start > end) - std::swap(start, end); - start.mColumn = 0; - // end.mColumn = end.mLine < mLines.size() ? mLines[end.mLine].size() : 0; - if (end.mColumn == 0 && end.mLine > 0) - --end.mLine; - if (end.mLine >= (int)mLines.size()) - end.mLine = mLines.empty() ? 0 : (int)mLines.size() - 1; - end.mColumn = GetLineMaxColumn(end.mLine); - - //if (end.mColumn >= GetLineMaxColumn(end.mLine)) - // end.mColumn = GetLineMaxColumn(end.mLine) - 1; - - u.mRemovedStart = start; - u.mRemovedEnd = end; - u.mRemoved = GetText(start, end); - - bool modified = false; - - for (int i = start.mLine; i <= end.mLine; i++) - { - auto& line = mLines[i]; - if (aShift) - { - if (!line.empty()) - { - if (line.front().mChar == '\t') - { - line.erase(line.begin()); - modified = true; - } - else - { - for (int j = 0; j < mTabSize && !line.empty() && line.front().mChar == ' '; j++) - { - line.erase(line.begin()); - modified = true; - } - } - } - } - else - { - line.insert(line.begin(), Glyph('\t', TextEditor::PaletteIndex::Background)); - modified = true; - } - } - - if (modified) - { - start = Coordinates(start.mLine, GetCharacterColumn(start.mLine, 0)); - Coordinates rangeEnd; - if (originalEnd.mColumn != 0) - { - end = Coordinates(end.mLine, GetLineMaxColumn(end.mLine)); - rangeEnd = end; - u.mAdded = GetText(start, end); - } - else - { - end = Coordinates(originalEnd.mLine, 0); - rangeEnd = Coordinates(end.mLine - 1, GetLineMaxColumn(end.mLine - 1)); - u.mAdded = GetText(start, rangeEnd); - } - - u.mAddedStart = start; - u.mAddedEnd = rangeEnd; - u.mAfter = mState; - - mState.mSelectionStart = start; - mState.mSelectionEnd = end; - AddUndo(u); - - mTextChanged = true; - - EnsureCursorVisible(); - } - - return; - } // c == '\t' - else - { - u.mRemoved = GetSelectedText(); - u.mRemovedStart = mState.mSelectionStart; - u.mRemovedEnd = mState.mSelectionEnd; - DeleteSelection(); - } - } // HasSelection - - auto coord = GetActualCursorCoordinates(); - u.mAddedStart = coord; - - assert(!mLines.empty()); - - if (aChar == '\n') - { - InsertLine(coord.mLine + 1); - auto& line = mLines[coord.mLine]; - auto& newLine = mLines[coord.mLine + 1]; - - if (mLanguageDefinition.mAutoIndentation) - for (size_t it = 0; it < line.size() && isascii(line[it].mChar) && isblank(line[it].mChar); ++it) - newLine.push_back(line[it]); - - const size_t whitespaceSize = newLine.size(); - auto cindex = GetCharacterIndex(coord); - newLine.insert(newLine.end(), line.begin() + cindex, line.end()); - line.erase(line.begin() + cindex, line.begin() + line.size()); - SetCursorPosition(Coordinates(coord.mLine + 1, GetCharacterColumn(coord.mLine + 1, (int)whitespaceSize))); - u.mAdded = (char)aChar; - } - else - { - char buf[7]; - int e = ImTextCharToUtf8(buf, 7, aChar); - if (e > 0) - { - buf[e] = '\0'; - auto& line = mLines[coord.mLine]; - auto cindex = GetCharacterIndex(coord); - - if (mOverwrite && cindex < (int)line.size()) - { - auto d = UTF8CharLength(line[cindex].mChar); - - u.mRemovedStart = mState.mCursorPosition; - u.mRemovedEnd = Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex + d)); - - while (d-- > 0 && cindex < (int)line.size()) - { - u.mRemoved += line[cindex].mChar; - line.erase(line.begin() + cindex); - } - } - - for (auto p = buf; *p != '\0'; p++, ++cindex) - line.insert(line.begin() + cindex, Glyph(*p, PaletteIndex::Default)); - u.mAdded = buf; - - SetCursorPosition(Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex))); - } - else - return; - } - - mTextChanged = true; - - u.mAddedEnd = GetActualCursorCoordinates(); - u.mAfter = mState; - - AddUndo(u); - - Colorize(coord.mLine - 1, 3); - EnsureCursorVisible(); -} - -void TextEditor::SetReadOnly(bool aValue) -{ - mReadOnly = aValue; -} - -void TextEditor::SetColorizerEnable(bool aValue) -{ - mColorizerEnabled = aValue; -} - -void TextEditor::SetCursorPosition(const Coordinates & aPosition) -{ - if (mState.mCursorPosition != aPosition) - { - mState.mCursorPosition = aPosition; - mCursorPositionChanged = true; - EnsureCursorVisible(); - } -} - -void TextEditor::SetSelectionStart(const Coordinates & aPosition) -{ - mState.mSelectionStart = SanitizeCoordinates(aPosition); - if (mState.mSelectionStart > mState.mSelectionEnd) - std::swap(mState.mSelectionStart, mState.mSelectionEnd); -} - -void TextEditor::SetSelectionEnd(const Coordinates & aPosition) -{ - mState.mSelectionEnd = SanitizeCoordinates(aPosition); - if (mState.mSelectionStart > mState.mSelectionEnd) - std::swap(mState.mSelectionStart, mState.mSelectionEnd); -} - -void TextEditor::SetSelection(const Coordinates & aStart, const Coordinates & aEnd, SelectionMode aMode) -{ - auto oldSelStart = mState.mSelectionStart; - auto oldSelEnd = mState.mSelectionEnd; - - mState.mSelectionStart = SanitizeCoordinates(aStart); - mState.mSelectionEnd = SanitizeCoordinates(aEnd); - if (mState.mSelectionStart > mState.mSelectionEnd) - std::swap(mState.mSelectionStart, mState.mSelectionEnd); - - switch (aMode) - { - case TextEditor::SelectionMode::Normal: - break; - case TextEditor::SelectionMode::Word: - { - mState.mSelectionStart = FindWordStart(mState.mSelectionStart); - if (!IsOnWordBoundary(mState.mSelectionEnd)) - mState.mSelectionEnd = FindWordEnd(FindWordStart(mState.mSelectionEnd)); - break; - } - case TextEditor::SelectionMode::Line: - { - const auto lineNo = mState.mSelectionEnd.mLine; - const auto lineSize = (size_t)lineNo < mLines.size() ? mLines[lineNo].size() : 0; - mState.mSelectionStart = Coordinates(mState.mSelectionStart.mLine, 0); - mState.mSelectionEnd = Coordinates(lineNo, GetLineMaxColumn(lineNo)); - break; - } - default: - break; - } - - if (mState.mSelectionStart != oldSelStart || - mState.mSelectionEnd != oldSelEnd) - mCursorPositionChanged = true; -} - -void TextEditor::SetTabSize(int aValue) -{ - mTabSize = std::max(0, std::min(32, aValue)); -} - -void TextEditor::InsertText(const std::string & aValue) -{ - InsertText(aValue.c_str()); -} - -void TextEditor::InsertText(const char * aValue) -{ - if (aValue == nullptr) - return; - - auto pos = GetActualCursorCoordinates(); - auto start = std::min(pos, mState.mSelectionStart); - int totalLines = pos.mLine - start.mLine; - - totalLines += InsertTextAt(pos, aValue); - - SetSelection(pos, pos); - SetCursorPosition(pos); - Colorize(start.mLine - 1, totalLines + 2); -} - -void TextEditor::DeleteSelection() -{ - assert(mState.mSelectionEnd >= mState.mSelectionStart); - - if (mState.mSelectionEnd == mState.mSelectionStart) - return; - - DeleteRange(mState.mSelectionStart, mState.mSelectionEnd); - - SetSelection(mState.mSelectionStart, mState.mSelectionStart); - SetCursorPosition(mState.mSelectionStart); - Colorize(mState.mSelectionStart.mLine, 1); -} - -void TextEditor::MoveUp(int aAmount, bool aSelect) -{ - auto oldPos = mState.mCursorPosition; - mState.mCursorPosition.mLine = std::max(0, mState.mCursorPosition.mLine - aAmount); - if (oldPos != mState.mCursorPosition) - { - if (aSelect) - { - if (oldPos == mInteractiveStart) - mInteractiveStart = mState.mCursorPosition; - else if (oldPos == mInteractiveEnd) - mInteractiveEnd = mState.mCursorPosition; - else - { - mInteractiveStart = mState.mCursorPosition; - mInteractiveEnd = oldPos; - } - } - else - mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; - SetSelection(mInteractiveStart, mInteractiveEnd); - - EnsureCursorVisible(); - } -} - -void TextEditor::MoveDown(int aAmount, bool aSelect) -{ - assert(mState.mCursorPosition.mColumn >= 0); - auto oldPos = mState.mCursorPosition; - mState.mCursorPosition.mLine = std::max(0, std::min((int)mLines.size() - 1, mState.mCursorPosition.mLine + aAmount)); - - if (mState.mCursorPosition != oldPos) - { - if (aSelect) - { - if (oldPos == mInteractiveEnd) - mInteractiveEnd = mState.mCursorPosition; - else if (oldPos == mInteractiveStart) - mInteractiveStart = mState.mCursorPosition; - else - { - mInteractiveStart = oldPos; - mInteractiveEnd = mState.mCursorPosition; - } - } - else - mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; - SetSelection(mInteractiveStart, mInteractiveEnd); - - EnsureCursorVisible(); - } -} - -static bool IsUTFSequence(char c) -{ - return (c & 0xC0) == 0x80; -} - -void TextEditor::MoveLeft(int aAmount, bool aSelect, bool aWordMode) -{ - if (mLines.empty()) - return; - - auto oldPos = mState.mCursorPosition; - mState.mCursorPosition = GetActualCursorCoordinates(); - auto line = mState.mCursorPosition.mLine; - auto cindex = GetCharacterIndex(mState.mCursorPosition); - - while (aAmount-- > 0) - { - if (cindex == 0) - { - if (line > 0) - { - --line; - if ((int)mLines.size() > line) - cindex = (int)mLines[line].size(); - else - cindex = 0; - } - } - else - { - --cindex; - if (cindex > 0) - { - if ((int)mLines.size() > line) - { - while (cindex > 0 && IsUTFSequence(mLines[line][cindex].mChar)) - --cindex; - } - } - } - - mState.mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex)); - if (aWordMode) - { - mState.mCursorPosition = FindWordStart(mState.mCursorPosition); - cindex = GetCharacterIndex(mState.mCursorPosition); - } - } - - mState.mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex)); - - assert(mState.mCursorPosition.mColumn >= 0); - if (aSelect) - { - if (oldPos == mInteractiveStart) - mInteractiveStart = mState.mCursorPosition; - else if (oldPos == mInteractiveEnd) - mInteractiveEnd = mState.mCursorPosition; - else - { - mInteractiveStart = mState.mCursorPosition; - mInteractiveEnd = oldPos; - } - } - else - mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; - SetSelection(mInteractiveStart, mInteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal); - - EnsureCursorVisible(); -} - -void TextEditor::MoveRight(int aAmount, bool aSelect, bool aWordMode) -{ - auto oldPos = mState.mCursorPosition; - - if (mLines.empty() || oldPos.mLine >= mLines.size()) - return; - - auto cindex = GetCharacterIndex(mState.mCursorPosition); - while (aAmount-- > 0) - { - auto lindex = mState.mCursorPosition.mLine; - auto& line = mLines[lindex]; - - if (cindex >= line.size()) - { - if (mState.mCursorPosition.mLine < mLines.size() - 1) - { - mState.mCursorPosition.mLine = std::max(0, std::min((int)mLines.size() - 1, mState.mCursorPosition.mLine + 1)); - mState.mCursorPosition.mColumn = 0; - } - else - return; - } - else - { - cindex += UTF8CharLength(line[cindex].mChar); - mState.mCursorPosition = Coordinates(lindex, GetCharacterColumn(lindex, cindex)); - if (aWordMode) - mState.mCursorPosition = FindNextWord(mState.mCursorPosition); - } - } - - if (aSelect) - { - if (oldPos == mInteractiveEnd) - mInteractiveEnd = SanitizeCoordinates(mState.mCursorPosition); - else if (oldPos == mInteractiveStart) - mInteractiveStart = mState.mCursorPosition; - else - { - mInteractiveStart = oldPos; - mInteractiveEnd = mState.mCursorPosition; - } - } - else - mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; - SetSelection(mInteractiveStart, mInteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal); - - EnsureCursorVisible(); -} - -void TextEditor::MoveTop(bool aSelect) -{ - auto oldPos = mState.mCursorPosition; - SetCursorPosition(Coordinates(0, 0)); - - if (mState.mCursorPosition != oldPos) - { - if (aSelect) - { - mInteractiveEnd = oldPos; - mInteractiveStart = mState.mCursorPosition; - } - else - mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; - SetSelection(mInteractiveStart, mInteractiveEnd); - } -} - -void TextEditor::TextEditor::MoveBottom(bool aSelect) -{ - auto oldPos = GetCursorPosition(); - auto newPos = Coordinates((int)mLines.size() - 1, 0); - SetCursorPosition(newPos); - if (aSelect) - { - mInteractiveStart = oldPos; - mInteractiveEnd = newPos; - } - else - mInteractiveStart = mInteractiveEnd = newPos; - SetSelection(mInteractiveStart, mInteractiveEnd); -} - -void TextEditor::MoveHome(bool aSelect) -{ - auto oldPos = mState.mCursorPosition; - SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, 0)); - - if (mState.mCursorPosition != oldPos) - { - if (aSelect) - { - if (oldPos == mInteractiveStart) - mInteractiveStart = mState.mCursorPosition; - else if (oldPos == mInteractiveEnd) - mInteractiveEnd = mState.mCursorPosition; - else - { - mInteractiveStart = mState.mCursorPosition; - mInteractiveEnd = oldPos; - } - } - else - mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; - SetSelection(mInteractiveStart, mInteractiveEnd); - } -} - -void TextEditor::MoveEnd(bool aSelect) -{ - auto oldPos = mState.mCursorPosition; - SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, GetLineMaxColumn(oldPos.mLine))); - - if (mState.mCursorPosition != oldPos) - { - if (aSelect) - { - if (oldPos == mInteractiveEnd) - mInteractiveEnd = mState.mCursorPosition; - else if (oldPos == mInteractiveStart) - mInteractiveStart = mState.mCursorPosition; - else - { - mInteractiveStart = oldPos; - mInteractiveEnd = mState.mCursorPosition; - } - } - else - mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; - SetSelection(mInteractiveStart, mInteractiveEnd); - } -} - -void TextEditor::Delete() -{ - assert(!mReadOnly); - - if (mLines.empty()) - return; - - UndoRecord u; - u.mBefore = mState; - - if (HasSelection()) - { - u.mRemoved = GetSelectedText(); - u.mRemovedStart = mState.mSelectionStart; - u.mRemovedEnd = mState.mSelectionEnd; - - DeleteSelection(); - } - else - { - auto pos = GetActualCursorCoordinates(); - SetCursorPosition(pos); - auto& line = mLines[pos.mLine]; - - if (pos.mColumn == GetLineMaxColumn(pos.mLine)) - { - if (pos.mLine == (int)mLines.size() - 1) - return; - - u.mRemoved = '\n'; - u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates(); - Advance(u.mRemovedEnd); - - auto& nextLine = mLines[pos.mLine + 1]; - line.insert(line.end(), nextLine.begin(), nextLine.end()); - RemoveLine(pos.mLine + 1); - } - else - { - auto cindex = GetCharacterIndex(pos); - u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates(); - u.mRemovedEnd.mColumn++; - u.mRemoved = GetText(u.mRemovedStart, u.mRemovedEnd); - - auto d = UTF8CharLength(line[cindex].mChar); - while (d-- > 0 && cindex < (int)line.size()) - line.erase(line.begin() + cindex); - } - - mTextChanged = true; - - Colorize(pos.mLine, 1); - } - - u.mAfter = mState; - AddUndo(u); -} - -void TextEditor::Backspace() -{ - assert(!mReadOnly); - - if (mLines.empty()) - return; - - UndoRecord u; - u.mBefore = mState; - - if (HasSelection()) - { - u.mRemoved = GetSelectedText(); - u.mRemovedStart = mState.mSelectionStart; - u.mRemovedEnd = mState.mSelectionEnd; - - DeleteSelection(); - } - else - { - auto pos = GetActualCursorCoordinates(); - SetCursorPosition(pos); - - if (mState.mCursorPosition.mColumn == 0) - { - if (mState.mCursorPosition.mLine == 0) - return; - - u.mRemoved = '\n'; - u.mRemovedStart = u.mRemovedEnd = Coordinates(pos.mLine - 1, GetLineMaxColumn(pos.mLine - 1)); - Advance(u.mRemovedEnd); - - auto& line = mLines[mState.mCursorPosition.mLine]; - auto& prevLine = mLines[mState.mCursorPosition.mLine - 1]; - auto prevSize = GetLineMaxColumn(mState.mCursorPosition.mLine - 1); - prevLine.insert(prevLine.end(), line.begin(), line.end()); - - ErrorMarkers etmp; - for (auto& i : mErrorMarkers) - etmp.insert(ErrorMarkers::value_type(i.first - 1 == mState.mCursorPosition.mLine ? i.first - 1 : i.first, i.second)); - mErrorMarkers = std::move(etmp); - - RemoveLine(mState.mCursorPosition.mLine); - --mState.mCursorPosition.mLine; - mState.mCursorPosition.mColumn = prevSize; - } - else - { - auto& line = mLines[mState.mCursorPosition.mLine]; - auto cindex = GetCharacterIndex(pos) - 1; - auto cend = cindex + 1; - while (cindex > 0 && IsUTFSequence(line[cindex].mChar)) - --cindex; - - //if (cindex > 0 && UTF8CharLength(line[cindex].mChar) > 1) - // --cindex; - - u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates(); - --u.mRemovedStart.mColumn; - --mState.mCursorPosition.mColumn; - - while (cindex < line.size() && cend-- > cindex) - { - u.mRemoved += line[cindex].mChar; - line.erase(line.begin() + cindex); - } - } - - mTextChanged = true; - - EnsureCursorVisible(); - Colorize(mState.mCursorPosition.mLine, 1); - } - - u.mAfter = mState; - AddUndo(u); -} - -void TextEditor::SelectWordUnderCursor() -{ - auto c = GetCursorPosition(); - SetSelection(FindWordStart(c), FindWordEnd(c)); -} - -void TextEditor::SelectAll() -{ - SetSelection(Coordinates(0, 0), Coordinates((int)mLines.size(), 0)); -} - -bool TextEditor::HasSelection() const -{ - return mState.mSelectionEnd > mState.mSelectionStart; -} - -void TextEditor::Copy() -{ - if (HasSelection()) - { - ImGui::SetClipboardText(GetSelectedText().c_str()); - } - else - { - if (!mLines.empty()) - { - std::string str; - auto& line = mLines[GetActualCursorCoordinates().mLine]; - for (auto& g : line) - str.push_back(g.mChar); - ImGui::SetClipboardText(str.c_str()); - } - } -} - -void TextEditor::Cut() -{ - if (IsReadOnly()) - { - Copy(); - } - else - { - if (HasSelection()) - { - UndoRecord u; - u.mBefore = mState; - u.mRemoved = GetSelectedText(); - u.mRemovedStart = mState.mSelectionStart; - u.mRemovedEnd = mState.mSelectionEnd; - - Copy(); - DeleteSelection(); - - u.mAfter = mState; - AddUndo(u); - } - } -} - -void TextEditor::Paste() -{ - if (IsReadOnly()) - return; - - auto clipText = ImGui::GetClipboardText(); - if (clipText != nullptr && strlen(clipText) > 0) - { - UndoRecord u; - u.mBefore = mState; - - if (HasSelection()) - { - u.mRemoved = GetSelectedText(); - u.mRemovedStart = mState.mSelectionStart; - u.mRemovedEnd = mState.mSelectionEnd; - DeleteSelection(); - } - - u.mAdded = clipText; - u.mAddedStart = GetActualCursorCoordinates(); - - InsertText(clipText); - - u.mAddedEnd = GetActualCursorCoordinates(); - u.mAfter = mState; - AddUndo(u); - } -} - -bool TextEditor::CanUndo() const -{ - return !mReadOnly && mUndoIndex > 0; -} - -bool TextEditor::CanRedo() const -{ - return !mReadOnly && mUndoIndex < (int)mUndoBuffer.size(); -} - -void TextEditor::Undo(int aSteps) -{ - while (CanUndo() && aSteps-- > 0) - mUndoBuffer[--mUndoIndex].Undo(this); -} - -void TextEditor::Redo(int aSteps) -{ - while (CanRedo() && aSteps-- > 0) - mUndoBuffer[mUndoIndex++].Redo(this); -} - -const TextEditor::Palette & TextEditor::GetDarkPalette() -{ - const static Palette p = { { - 0xff7f7f7f, // Default - 0xffd69c56, // Keyword - 0xff00ff00, // Number - 0xff7070e0, // String - 0xff70a0e0, // Char literal - 0xffffffff, // Punctuation - 0xff408080, // Preprocessor - 0xffaaaaaa, // Identifier - 0xff9bc64d, // Known identifier - 0xffc040a0, // Preproc identifier - 0xff206020, // Comment (single line) - 0xff406020, // Comment (multi line) - 0xff101010, // Background - 0xffe0e0e0, // Cursor - 0x80a06020, // Selection - 0x800020ff, // ErrorMarker - 0x40f08000, // Breakpoint - 0xff707000, // Line number - 0x40000000, // Current line fill - 0x40808080, // Current line fill (inactive) - 0x40a0a0a0, // Current line edge - } }; - return p; -} - -const TextEditor::Palette & TextEditor::GetLightPalette() -{ - const static Palette p = { { - 0xff7f7f7f, // None - 0xffff0c06, // Keyword - 0xff008000, // Number - 0xff2020a0, // String - 0xff304070, // Char literal - 0xff000000, // Punctuation - 0xff406060, // Preprocessor - 0xff404040, // Identifier - 0xff606010, // Known identifier - 0xffc040a0, // Preproc identifier - 0xff205020, // Comment (single line) - 0xff405020, // Comment (multi line) - 0xffffffff, // Background - 0xff000000, // Cursor - 0x80600000, // Selection - 0xa00010ff, // ErrorMarker - 0x80f08000, // Breakpoint - 0xff505000, // Line number - 0x40000000, // Current line fill - 0x40808080, // Current line fill (inactive) - 0x40000000, // Current line edge - } }; - return p; -} - -const TextEditor::Palette & TextEditor::GetRetroBluePalette() -{ - const static Palette p = { { - 0xff00ffff, // None - 0xffffff00, // Keyword - 0xff00ff00, // Number - 0xff808000, // String - 0xff808000, // Char literal - 0xffffffff, // Punctuation - 0xff008000, // Preprocessor - 0xff00ffff, // Identifier - 0xffffffff, // Known identifier - 0xffff00ff, // Preproc identifier - 0xff808080, // Comment (single line) - 0xff404040, // Comment (multi line) - 0xff800000, // Background - 0xff0080ff, // Cursor - 0x80ffff00, // Selection - 0xa00000ff, // ErrorMarker - 0x80ff8000, // Breakpoint - 0xff808000, // Line number - 0x40000000, // Current line fill - 0x40808080, // Current line fill (inactive) - 0x40000000, // Current line edge - } }; - return p; -} - - -std::string TextEditor::GetText() const -{ - return GetText(Coordinates(), Coordinates((int)mLines.size(), 0)); -} - -std::vector TextEditor::GetTextLines() const -{ - std::vector result; - - result.reserve(mLines.size()); - - for (auto & line : mLines) - { - std::string text; - - text.resize(line.size()); - - for (size_t i = 0; i < line.size(); ++i) - text[i] = line[i].mChar; - - result.emplace_back(std::move(text)); - } - - return result; -} - -std::string TextEditor::GetSelectedText() const -{ - return GetText(mState.mSelectionStart, mState.mSelectionEnd); -} - -std::string TextEditor::GetCurrentLineText()const -{ - auto lineLength = GetLineMaxColumn(mState.mCursorPosition.mLine); - return GetText( - Coordinates(mState.mCursorPosition.mLine, 0), - Coordinates(mState.mCursorPosition.mLine, lineLength)); -} - -void TextEditor::ProcessInputs() -{ + mUndoBuffer.resize((size_t)(mUndoIndex + 1)); + mUndoBuffer.back() = aValue; + ++mUndoIndex; } +// TODO +// - multiline comments vs single-line: latter is blocking start of a ML void TextEditor::Colorize(int aFromLine, int aLines) { int toLine = aLines == -1 ? (int)mLines.size() : std::min((int)mLines.size(), aFromLine + aLines); @@ -2143,11 +2572,11 @@ void TextEditor::Colorize(int aFromLine, int aLines) void TextEditor::ColorizeRange(int aFromLine, int aToLine) { - if (mLines.empty() || aFromLine >= aToLine) + if (mLines.empty() || aFromLine >= aToLine || mLanguageDefinition == nullptr) return; std::string buffer; - std::cmatch results; + boost::cmatch results; std::string id; int endLine = std::max(0, std::min((int)mLines.size(), aToLine)); @@ -2166,22 +2595,22 @@ void TextEditor::ColorizeRange(int aFromLine, int aToLine) col.mColorIndex = PaletteIndex::Default; } - const char * bufferBegin = &buffer.front(); - const char * bufferEnd = bufferBegin + buffer.size(); + const char* bufferBegin = &buffer.front(); + const char* bufferEnd = bufferBegin + buffer.size(); auto last = bufferEnd; for (auto first = bufferBegin; first != last; ) { - const char * token_begin = nullptr; - const char * token_end = nullptr; + const char* token_begin = nullptr; + const char* token_end = nullptr; PaletteIndex token_color = PaletteIndex::Default; bool hasTokenizeResult = false; - if (mLanguageDefinition.mTokenize != nullptr) + if (mLanguageDefinition->mTokenize != nullptr) { - if (mLanguageDefinition.mTokenize(first, last, token_begin, token_end, token_color)) + if (mLanguageDefinition->mTokenize(first, last, token_begin, token_end, token_color)) hasTokenizeResult = true; } @@ -2190,9 +2619,12 @@ void TextEditor::ColorizeRange(int aFromLine, int aToLine) // todo : remove //printf("using regex for %.*s\n", first + 10 < last ? 10 : int(last - first), first); - for (auto& p : mRegexList) + for (const auto& p : mRegexList->mValue) { - if (std::regex_search(first, last, results, p.first, std::regex_constants::match_continuous)) + bool regexSearchResult = false; + try { regexSearchResult = boost::regex_search(first, last, results, p.first, boost::regex_constants::match_continuous); } + catch (...) {} + if (regexSearchResult) { hasTokenizeResult = true; @@ -2218,21 +2650,21 @@ void TextEditor::ColorizeRange(int aFromLine, int aToLine) id.assign(token_begin, token_end); // todo : allmost all language definitions use lower case to specify keywords, so shouldn't this use ::tolower ? - if (!mLanguageDefinition.mCaseSensitive) + if (!mLanguageDefinition->mCaseSensitive) std::transform(id.begin(), id.end(), id.begin(), ::toupper); if (!line[first - bufferBegin].mPreprocessor) { - if (mLanguageDefinition.mKeywords.count(id) != 0) + if (mLanguageDefinition->mKeywords.count(id) != 0) token_color = PaletteIndex::Keyword; - else if (mLanguageDefinition.mIdentifiers.count(id) != 0) + else if (mLanguageDefinition->mIdentifiers.count(id) != 0) token_color = PaletteIndex::KnownIdentifier; - else if (mLanguageDefinition.mPreprocIdentifiers.count(id) != 0) + else if (mLanguageDefinition->mPreprocIdentifiers.count(id) != 0) token_color = PaletteIndex::PreprocIdentifier; } else { - if (mLanguageDefinition.mPreprocIdentifiers.count(id) != 0) + if (mLanguageDefinition->mPreprocIdentifiers.count(id) != 0) token_color = PaletteIndex::PreprocIdentifier; } } @@ -2246,9 +2678,20 @@ void TextEditor::ColorizeRange(int aFromLine, int aToLine) } } +template +bool ColorizerEquals(InputIt1 first1, InputIt1 last1, + InputIt2 first2, InputIt2 last2, BinaryPredicate p) +{ + for (; first1 != last1 && first2 != last2; ++first1, ++first2) + { + if (!p(*first1, *first2)) + return false; + } + return first1 == last1 && first2 == last2; +} void TextEditor::ColorizeInternal() { - if (mLines.empty() || !mColorizerEnabled) + if (mLines.empty() || mLanguageDefinition == nullptr) return; if (mCheckComments) @@ -2282,7 +2725,7 @@ void TextEditor::ColorizeInternal() auto& g = line[currentIndex]; auto c = g.mChar; - if (c != mLanguageDefinition.mPreprocChar && !isspace(c)) + if (c != mLanguageDefinition->mPreprocChar && !isspace(c)) firstChar = false; if (currentIndex == (int)line.size() - 1 && line[line.size() - 1].mChar == '\\') @@ -2314,7 +2757,7 @@ void TextEditor::ColorizeInternal() } else { - if (firstChar && c == mLanguageDefinition.mPreprocChar) + if (firstChar && c == mLanguageDefinition->mPreprocChar) withinPreproc = true; if (c == '\"') @@ -2326,37 +2769,38 @@ void TextEditor::ColorizeInternal() { auto pred = [](const char& a, const Glyph& b) { return a == b.mChar; }; auto from = line.begin() + currentIndex; - auto& startStr = mLanguageDefinition.mCommentStart; - auto& singleStartStr = mLanguageDefinition.mSingleLineComment; + auto& startStr = mLanguageDefinition->mCommentStart; + auto& singleStartStr = mLanguageDefinition->mSingleLineComment; - if (singleStartStr.size() > 0 && - currentIndex + singleStartStr.size() <= line.size() && - equals(singleStartStr.begin(), singleStartStr.end(), from, from + singleStartStr.size(), pred)) - { - withinSingleLineComment = true; - } - else if (!withinSingleLineComment && currentIndex + startStr.size() <= line.size() && - equals(startStr.begin(), startStr.end(), from, from + startStr.size(), pred)) + if (!withinSingleLineComment && currentIndex + startStr.size() <= line.size() && + ColorizerEquals(startStr.begin(), startStr.end(), from, from + startStr.size(), pred)) { commentStartLine = currentLine; commentStartIndex = currentIndex; } + else if (singleStartStr.size() > 0 && + currentIndex + singleStartStr.size() <= line.size() && + ColorizerEquals(singleStartStr.begin(), singleStartStr.end(), from, from + singleStartStr.size(), pred)) + { + withinSingleLineComment = true; + } - inComment = inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex)); + inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex)); line[currentIndex].mMultiLineComment = inComment; line[currentIndex].mComment = withinSingleLineComment; - auto& endStr = mLanguageDefinition.mCommentEnd; + auto& endStr = mLanguageDefinition->mCommentEnd; if (currentIndex + 1 >= (int)endStr.size() && - equals(endStr.begin(), endStr.end(), from + 1 - endStr.size(), from + 1, pred)) + ColorizerEquals(endStr.begin(), endStr.end(), from + 1 - endStr.size(), from + 1, pred)) { commentStartIndex = endIndex; commentStartLine = endLine; } } } - line[currentIndex].mPreprocessor = withinPreproc; + if (currentIndex < (int)line.size()) + line[currentIndex].mPreprocessor = withinPreproc; currentIndex += UTF8CharLength(c); if (currentIndex >= (int)line.size()) { @@ -2375,7 +2819,7 @@ void TextEditor::ColorizeInternal() if (mColorRangeMin < mColorRangeMax) { - const int increment = (mLanguageDefinition.mTokenize == nullptr) ? 10 : 10000; + const int increment = (mLanguageDefinition->mTokenize == nullptr) ? 10 : 10000; const int to = std::min(mColorRangeMin + increment, mColorRangeMax); ColorizeRange(mColorRangeMin, to); mColorRangeMin = to; @@ -2389,772 +2833,130 @@ void TextEditor::ColorizeInternal() } } -float TextEditor::TextDistanceToLineStart(const Coordinates& aFrom) const +const TextEditor::Palette& TextEditor::GetDarkPalette() { - auto& line = mLines[aFrom.mLine]; - float distance = 0.0f; - float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x; - int colIndex = GetCharacterIndex(aFrom); - for (size_t it = 0u; it < line.size() && it < colIndex; ) - { - if (line[it].mChar == '\t') - { - distance = (1.0f + std::floor((1.0f + distance) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); - ++it; - } - else - { - auto d = UTF8CharLength(line[it].mChar); - char tempCString[7]; - int i = 0; - for (; i < 6 && d-- > 0 && it < (int)line.size(); i++, it++) - tempCString[i] = line[it].mChar; - - tempCString[i] = '\0'; - distance += ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, tempCString, nullptr, nullptr).x; - } - } - - return distance; + const static Palette p = { { + 0xdcdfe4ff, // Default + 0xe06c75ff, // Keyword + 0xe5c07bff, // Number + 0x98c379ff, // String + 0xe0a070ff, // Char literal + 0x6a7384ff, // Punctuation + 0x808040ff, // Preprocessor + 0xdcdfe4ff, // Identifier + 0x61afefff, // Known identifier + 0xc678ddff, // Preproc identifier + 0x3696a2ff, // Comment (single line) + 0x3696a2ff, // Comment (multi line) + 0x282c34ff, // Background + 0xe0e0e0ff, // Cursor + 0x2060a080, // Selection + 0xff200080, // ErrorMarker + 0xffffff15, // ControlCharacter + 0x0080f040, // Breakpoint + 0x7a8394ff, // Line number + 0x00000040, // Current line fill + 0x80808040, // Current line fill (inactive) + 0xa0a0a040, // Current line edge + } }; + return p; } -void TextEditor::EnsureCursorVisible() +const TextEditor::Palette& TextEditor::GetMarianaPalette() { - if (!mWithinRender) - { - mScrollToCursor = true; - return; - } - - float scrollX = ImGui::GetScrollX(); - float scrollY = ImGui::GetScrollY(); - - auto height = ImGui::GetWindowHeight(); - auto width = ImGui::GetWindowWidth(); - - auto top = 1 + (int)ceil(scrollY / mCharAdvance.y); - auto bottom = (int)ceil((scrollY + height) / mCharAdvance.y); - - auto left = (int)ceil(scrollX / mCharAdvance.x); - auto right = (int)ceil((scrollX + width) / mCharAdvance.x); - - auto pos = GetActualCursorCoordinates(); - auto len = TextDistanceToLineStart(pos); - - if (pos.mLine < top) - ImGui::SetScrollY(std::max(0.0f, (pos.mLine - 1) * mCharAdvance.y)); - if (pos.mLine > bottom - 4) - ImGui::SetScrollY(std::max(0.0f, (pos.mLine + 4) * mCharAdvance.y - height)); - if (len + mTextStart < left + 4) - ImGui::SetScrollX(std::max(0.0f, len + mTextStart - 4)); - if (len + mTextStart > right - 4) - ImGui::SetScrollX(std::max(0.0f, len + mTextStart + 4 - width)); + const static Palette p = { { + 0xffffffff, // Default + 0xc695c6ff, // Keyword + 0xf9ae58ff, // Number + 0x99c794ff, // String + 0xe0a070ff, // Char literal + 0x5fb4b4ff, // Punctuation + 0x808040ff, // Preprocessor + 0xffffffff, // Identifier + 0x4dc69bff, // Known identifier + 0xe0a0ffff, // Preproc identifier + 0xa6acb9ff, // Comment (single line) + 0xa6acb9ff, // Comment (multi line) + 0x303841ff, // Background + 0xe0e0e0ff, // Cursor + 0x6e7a8580, // Selection + 0xec5f6680, // ErrorMarker + 0xffffff30, // ControlCharacter + 0x0080f040, // Breakpoint + 0xffffffb0, // Line number + 0x4e5a6580, // Current line fill + 0x4e5a6530, // Current line fill (inactive) + 0x4e5a65b0, // Current line edge + } }; + return p; } -int TextEditor::GetPageSize() const +const TextEditor::Palette& TextEditor::GetLightPalette() { - auto height = ImGui::GetWindowHeight() - 20.0f; - return (int)floor(height / mCharAdvance.y); + const static Palette p = { { + 0x404040ff, // None + 0x060cffff, // Keyword + 0x008000ff, // Number + 0xa02020ff, // String + 0x704030ff, // Char literal + 0x000000ff, // Punctuation + 0x606040ff, // Preprocessor + 0x404040ff, // Identifier + 0x106060ff, // Known identifier + 0xa040c0ff, // Preproc identifier + 0x205020ff, // Comment (single line) + 0x205040ff, // Comment (multi line) + 0xffffffff, // Background + 0x000000ff, // Cursor + 0x00006040, // Selection + 0xff1000a0, // ErrorMarker + 0x90909090, // ControlCharacter + 0x0080f080, // Breakpoint + 0x005050ff, // Line number + 0x00000040, // Current line fill + 0x80808040, // Current line fill (inactive) + 0x00000040, // Current line edge + } }; + return p; } -TextEditor::UndoRecord::UndoRecord( - const std::string& aAdded, - const TextEditor::Coordinates aAddedStart, - const TextEditor::Coordinates aAddedEnd, - const std::string& aRemoved, - const TextEditor::Coordinates aRemovedStart, - const TextEditor::Coordinates aRemovedEnd, - TextEditor::EditorState& aBefore, - TextEditor::EditorState& aAfter) - : mAdded(aAdded) - , mAddedStart(aAddedStart) - , mAddedEnd(aAddedEnd) - , mRemoved(aRemoved) - , mRemovedStart(aRemovedStart) - , mRemovedEnd(aRemovedEnd) - , mBefore(aBefore) - , mAfter(aAfter) +const TextEditor::Palette& TextEditor::GetRetroBluePalette() { - assert(mAddedStart <= mAddedEnd); - assert(mRemovedStart <= mRemovedEnd); + const static Palette p = { { + 0xffff00ff, // None + 0x00ffffff, // Keyword + 0x00ff00ff, // Number + 0x008080ff, // String + 0x008080ff, // Char literal + 0xffffffff, // Punctuation + 0x008000ff, // Preprocessor + 0xffff00ff, // Identifier + 0xffffffff, // Known identifier + 0xff00ffff, // Preproc identifier + 0x808080ff, // Comment (single line) + 0x404040ff, // Comment (multi line) + 0x000080ff, // Background + 0xff8000ff, // Cursor + 0x00ffff80, // Selection + 0xff0000a0, // ErrorMarker + 0x0080ff80, // Breakpoint + 0x008080ff, // Line number + 0x00000040, // Current line fill + 0x80808040, // Current line fill (inactive) + 0x00000040, // Current line edge + } }; + return p; } -void TextEditor::UndoRecord::Undo(TextEditor * aEditor) -{ - if (!mAdded.empty()) - { - aEditor->DeleteRange(mAddedStart, mAddedEnd); - aEditor->Colorize(mAddedStart.mLine - 1, mAddedEnd.mLine - mAddedStart.mLine + 2); - } - - if (!mRemoved.empty()) - { - auto start = mRemovedStart; - aEditor->InsertTextAt(start, mRemoved.c_str()); - aEditor->Colorize(mRemovedStart.mLine - 1, mRemovedEnd.mLine - mRemovedStart.mLine + 2); - } - - aEditor->mState = mBefore; - aEditor->EnsureCursorVisible(); - -} - -void TextEditor::UndoRecord::Redo(TextEditor * aEditor) -{ - if (!mRemoved.empty()) - { - aEditor->DeleteRange(mRemovedStart, mRemovedEnd); - aEditor->Colorize(mRemovedStart.mLine - 1, mRemovedEnd.mLine - mRemovedStart.mLine + 1); - } - - if (!mAdded.empty()) - { - auto start = mAddedStart; - aEditor->InsertTextAt(start, mAdded.c_str()); - aEditor->Colorize(mAddedStart.mLine - 1, mAddedEnd.mLine - mAddedStart.mLine + 1); - } - - aEditor->mState = mAfter; - aEditor->EnsureCursorVisible(); -} - -static bool TokenizeCStyleString(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end) -{ - const char * p = in_begin; - - if (*p == '"') - { - p++; - - while (p < in_end) - { - // handle end of string - if (*p == '"') - { - out_begin = in_begin; - out_end = p + 1; - return true; - } - - // handle escape character for " - if (*p == '\\' && p + 1 < in_end && p[1] == '"') - p++; - - p++; - } - } - - return false; -} - -static bool TokenizeCStyleCharacterLiteral(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end) -{ - const char * p = in_begin; - - if (*p == '\'') - { - p++; - - // handle escape characters - if (p < in_end && *p == '\\') - p++; - - if (p < in_end) - p++; - - // handle end of character literal - if (p < in_end && *p == '\'') - { - out_begin = in_begin; - out_end = p + 1; - return true; - } - } - - return false; -} - -static bool TokenizeCStyleIdentifier(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end) -{ - const char * p = in_begin; - - if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_') - { - p++; - - while ((p < in_end) && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == '_')) - p++; - - out_begin = in_begin; - out_end = p; - return true; - } - - return false; -} - -static bool TokenizeCStyleNumber(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end) -{ - const char * p = in_begin; - - const bool startsWithNumber = *p >= '0' && *p <= '9'; - - if (*p != '+' && *p != '-' && !startsWithNumber) - return false; - - p++; - - bool hasNumber = startsWithNumber; - - while (p < in_end && (*p >= '0' && *p <= '9')) - { - hasNumber = true; - - p++; - } - - if (hasNumber == false) - return false; - - bool isFloat = false; - bool isHex = false; - bool isBinary = false; - - if (p < in_end) - { - if (*p == '.') - { - isFloat = true; - - p++; - - while (p < in_end && (*p >= '0' && *p <= '9')) - p++; - } - else if (*p == 'x' || *p == 'X') - { - // hex formatted integer of the type 0xef80 - - isHex = true; - - p++; - - while (p < in_end && ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F'))) - p++; - } - else if (*p == 'b' || *p == 'B') - { - // binary formatted integer of the type 0b01011101 - - isBinary = true; - - p++; - - while (p < in_end && (*p >= '0' && *p <= '1')) - p++; - } - } - - if (isHex == false && isBinary == false) - { - // floating point exponent - if (p < in_end && (*p == 'e' || *p == 'E')) - { - isFloat = true; - - p++; - - if (p < in_end && (*p == '+' || *p == '-')) - p++; - - bool hasDigits = false; - - while (p < in_end && (*p >= '0' && *p <= '9')) - { - hasDigits = true; - - p++; - } - - if (hasDigits == false) - return false; - } - - // single precision floating point type - if (p < in_end && *p == 'f') - p++; - } - - if (isFloat == false) - { - // integer size type - while (p < in_end && (*p == 'u' || *p == 'U' || *p == 'l' || *p == 'L')) - p++; - } - - out_begin = in_begin; - out_end = p; - return true; -} - -static bool TokenizeCStylePunctuation(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end) -{ - (void)in_end; - - switch (*in_begin) - { - case '[': - case ']': - case '{': - case '}': - case '!': - case '%': - case '^': - case '&': - case '*': - case '(': - case ')': - case '-': - case '+': - case '=': - case '~': - case '|': - case '<': - case '>': - case '?': - case ':': - case '/': - case ';': - case ',': - case '.': - out_begin = in_begin; - out_end = in_begin + 1; - return true; - } - - return false; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::CPlusPlus() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const cppKeywords[] = { - "alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit", "atomic_noexcept", "auto", "bitand", "bitor", "bool", "break", "case", "catch", "char", "char16_t", "char32_t", "class", - "compl", "concept", "const", "constexpr", "const_cast", "continue", "decltype", "default", "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float", - "for", "friend", "goto", "if", "import", "inline", "int", "long", "module", "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private", "protected", "public", - "register", "reinterpret_cast", "requires", "return", "short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "synchronized", "template", "this", "thread_local", - "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq" - }; - for (auto& k : cppKeywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", - "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "printf", "sprintf", "snprintf", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper", - "std", "string", "vector", "map", "unordered_map", "set", "unordered_set", "min", "max" - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - langDef.mTokenize = [](const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end, PaletteIndex & paletteIndex) -> bool - { - paletteIndex = PaletteIndex::Max; - - while (in_begin < in_end && isascii(*in_begin) && isblank(*in_begin)) - in_begin++; - - if (in_begin == in_end) - { - out_begin = in_end; - out_end = in_end; - paletteIndex = PaletteIndex::Default; - } - else if (TokenizeCStyleString(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::String; - else if (TokenizeCStyleCharacterLiteral(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::CharLiteral; - else if (TokenizeCStyleIdentifier(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Identifier; - else if (TokenizeCStyleNumber(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Number; - else if (TokenizeCStylePunctuation(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Punctuation; - - return paletteIndex != PaletteIndex::Max; - }; - - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; - - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; - - langDef.mName = "C++"; - - inited = true; - } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::HLSL() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const keywords[] = { - "AppendStructuredBuffer", "asm", "asm_fragment", "BlendState", "bool", "break", "Buffer", "ByteAddressBuffer", "case", "cbuffer", "centroid", "class", "column_major", "compile", "compile_fragment", - "CompileShader", "const", "continue", "ComputeShader", "ConsumeStructuredBuffer", "default", "DepthStencilState", "DepthStencilView", "discard", "do", "double", "DomainShader", "dword", "else", - "export", "extern", "false", "float", "for", "fxgroup", "GeometryShader", "groupshared", "half", "Hullshader", "if", "in", "inline", "inout", "InputPatch", "int", "interface", "line", "lineadj", - "linear", "LineStream", "matrix", "min16float", "min10float", "min16int", "min12int", "min16uint", "namespace", "nointerpolation", "noperspective", "NULL", "out", "OutputPatch", "packoffset", - "pass", "pixelfragment", "PixelShader", "point", "PointStream", "precise", "RasterizerState", "RenderTargetView", "return", "register", "row_major", "RWBuffer", "RWByteAddressBuffer", "RWStructuredBuffer", - "RWTexture1D", "RWTexture1DArray", "RWTexture2D", "RWTexture2DArray", "RWTexture3D", "sample", "sampler", "SamplerState", "SamplerComparisonState", "shared", "snorm", "stateblock", "stateblock_state", - "static", "string", "struct", "switch", "StructuredBuffer", "tbuffer", "technique", "technique10", "technique11", "texture", "Texture1D", "Texture1DArray", "Texture2D", "Texture2DArray", "Texture2DMS", - "Texture2DMSArray", "Texture3D", "TextureCube", "TextureCubeArray", "true", "typedef", "triangle", "triangleadj", "TriangleStream", "uint", "uniform", "unorm", "unsigned", "vector", "vertexfragment", - "VertexShader", "void", "volatile", "while", - "bool1","bool2","bool3","bool4","double1","double2","double3","double4", "float1", "float2", "float3", "float4", "int1", "int2", "int3", "int4", "in", "out", "inout", - "uint1", "uint2", "uint3", "uint4", "dword1", "dword2", "dword3", "dword4", "half1", "half2", "half3", "half4", - "float1x1","float2x1","float3x1","float4x1","float1x2","float2x2","float3x2","float4x2", - "float1x3","float2x3","float3x3","float4x3","float1x4","float2x4","float3x4","float4x4", - "half1x1","half2x1","half3x1","half4x1","half1x2","half2x2","half3x2","half4x2", - "half1x3","half2x3","half3x3","half4x3","half1x4","half2x4","half3x4","half4x4", - }; - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "abort", "abs", "acos", "all", "AllMemoryBarrier", "AllMemoryBarrierWithGroupSync", "any", "asdouble", "asfloat", "asin", "asint", "asint", "asuint", - "asuint", "atan", "atan2", "ceil", "CheckAccessFullyMapped", "clamp", "clip", "cos", "cosh", "countbits", "cross", "D3DCOLORtoUBYTE4", "ddx", - "ddx_coarse", "ddx_fine", "ddy", "ddy_coarse", "ddy_fine", "degrees", "determinant", "DeviceMemoryBarrier", "DeviceMemoryBarrierWithGroupSync", - "distance", "dot", "dst", "errorf", "EvaluateAttributeAtCentroid", "EvaluateAttributeAtSample", "EvaluateAttributeSnapped", "exp", "exp2", - "f16tof32", "f32tof16", "faceforward", "firstbithigh", "firstbitlow", "floor", "fma", "fmod", "frac", "frexp", "fwidth", "GetRenderTargetSampleCount", - "GetRenderTargetSamplePosition", "GroupMemoryBarrier", "GroupMemoryBarrierWithGroupSync", "InterlockedAdd", "InterlockedAnd", "InterlockedCompareExchange", - "InterlockedCompareStore", "InterlockedExchange", "InterlockedMax", "InterlockedMin", "InterlockedOr", "InterlockedXor", "isfinite", "isinf", "isnan", - "ldexp", "length", "lerp", "lit", "log", "log10", "log2", "mad", "max", "min", "modf", "msad4", "mul", "noise", "normalize", "pow", "printf", - "Process2DQuadTessFactorsAvg", "Process2DQuadTessFactorsMax", "Process2DQuadTessFactorsMin", "ProcessIsolineTessFactors", "ProcessQuadTessFactorsAvg", - "ProcessQuadTessFactorsMax", "ProcessQuadTessFactorsMin", "ProcessTriTessFactorsAvg", "ProcessTriTessFactorsMax", "ProcessTriTessFactorsMin", - "radians", "rcp", "reflect", "refract", "reversebits", "round", "rsqrt", "saturate", "sign", "sin", "sincos", "sinh", "smoothstep", "sqrt", "step", - "tan", "tanh", "tex1D", "tex1D", "tex1Dbias", "tex1Dgrad", "tex1Dlod", "tex1Dproj", "tex2D", "tex2D", "tex2Dbias", "tex2Dgrad", "tex2Dlod", "tex2Dproj", - "tex3D", "tex3D", "tex3Dbias", "tex3Dgrad", "tex3Dlod", "tex3Dproj", "texCUBE", "texCUBE", "texCUBEbias", "texCUBEgrad", "texCUBElod", "texCUBEproj", "transpose", "trunc" - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - langDef.mTokenRegexStrings.push_back(std::make_pair("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor)); - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; - - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; - - langDef.mName = "HLSL"; - - inited = true; - } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::GLSL() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const keywords[] = { - "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short", - "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", - "_Noreturn", "_Static_assert", "_Thread_local" - }; - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", - "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper" - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - langDef.mTokenRegexStrings.push_back(std::make_pair("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor)); - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; - - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; - - langDef.mName = "GLSL"; - - inited = true; - } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::C() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const keywords[] = { - "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short", - "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", - "_Noreturn", "_Static_assert", "_Thread_local" - }; - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", - "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper" - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - langDef.mTokenize = [](const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end, PaletteIndex & paletteIndex) -> bool - { - paletteIndex = PaletteIndex::Max; - - while (in_begin < in_end && isascii(*in_begin) && isblank(*in_begin)) - in_begin++; - - if (in_begin == in_end) - { - out_begin = in_end; - out_end = in_end; - paletteIndex = PaletteIndex::Default; - } - else if (TokenizeCStyleString(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::String; - else if (TokenizeCStyleCharacterLiteral(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::CharLiteral; - else if (TokenizeCStyleIdentifier(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Identifier; - else if (TokenizeCStyleNumber(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Number; - else if (TokenizeCStylePunctuation(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Punctuation; - - return paletteIndex != PaletteIndex::Max; - }; - - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; - - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; - - langDef.mName = "C"; - - inited = true; - } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::SQL() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const keywords[] = { - "ADD", "EXCEPT", "PERCENT", "ALL", "EXEC", "PLAN", "ALTER", "EXECUTE", "PRECISION", "AND", "EXISTS", "PRIMARY", "ANY", "EXIT", "PRINT", "AS", "FETCH", "PROC", "ASC", "FILE", "PROCEDURE", - "AUTHORIZATION", "FILLFACTOR", "PUBLIC", "BACKUP", "FOR", "RAISERROR", "BEGIN", "FOREIGN", "READ", "BETWEEN", "FREETEXT", "READTEXT", "BREAK", "FREETEXTTABLE", "RECONFIGURE", - "BROWSE", "FROM", "REFERENCES", "BULK", "FULL", "REPLICATION", "BY", "FUNCTION", "RESTORE", "CASCADE", "GOTO", "RESTRICT", "CASE", "GRANT", "RETURN", "CHECK", "GROUP", "REVOKE", - "CHECKPOINT", "HAVING", "RIGHT", "CLOSE", "HOLDLOCK", "ROLLBACK", "CLUSTERED", "IDENTITY", "ROWCOUNT", "COALESCE", "IDENTITY_INSERT", "ROWGUIDCOL", "COLLATE", "IDENTITYCOL", "RULE", - "COLUMN", "IF", "SAVE", "COMMIT", "IN", "SCHEMA", "COMPUTE", "INDEX", "SELECT", "CONSTRAINT", "INNER", "SESSION_USER", "CONTAINS", "INSERT", "SET", "CONTAINSTABLE", "INTERSECT", "SETUSER", - "CONTINUE", "INTO", "SHUTDOWN", "CONVERT", "IS", "SOME", "CREATE", "JOIN", "STATISTICS", "CROSS", "KEY", "SYSTEM_USER", "CURRENT", "KILL", "TABLE", "CURRENT_DATE", "LEFT", "TEXTSIZE", - "CURRENT_TIME", "LIKE", "THEN", "CURRENT_TIMESTAMP", "LINENO", "TO", "CURRENT_USER", "LOAD", "TOP", "CURSOR", "NATIONAL", "TRAN", "DATABASE", "NOCHECK", "TRANSACTION", - "DBCC", "NONCLUSTERED", "TRIGGER", "DEALLOCATE", "NOT", "TRUNCATE", "DECLARE", "NULL", "TSEQUAL", "DEFAULT", "NULLIF", "UNION", "DELETE", "OF", "UNIQUE", "DENY", "OFF", "UPDATE", - "DESC", "OFFSETS", "UPDATETEXT", "DISK", "ON", "USE", "DISTINCT", "OPEN", "USER", "DISTRIBUTED", "OPENDATASOURCE", "VALUES", "DOUBLE", "OPENQUERY", "VARYING","DROP", "OPENROWSET", "VIEW", - "DUMMY", "OPENXML", "WAITFOR", "DUMP", "OPTION", "WHEN", "ELSE", "OR", "WHERE", "END", "ORDER", "WHILE", "ERRLVL", "OUTER", "WITH", "ESCAPE", "OVER", "WRITETEXT" - }; - - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "ABS", "ACOS", "ADD_MONTHS", "ASCII", "ASCIISTR", "ASIN", "ATAN", "ATAN2", "AVG", "BFILENAME", "BIN_TO_NUM", "BITAND", "CARDINALITY", "CASE", "CAST", "CEIL", - "CHARTOROWID", "CHR", "COALESCE", "COMPOSE", "CONCAT", "CONVERT", "CORR", "COS", "COSH", "COUNT", "COVAR_POP", "COVAR_SAMP", "CUME_DIST", "CURRENT_DATE", - "CURRENT_TIMESTAMP", "DBTIMEZONE", "DECODE", "DECOMPOSE", "DENSE_RANK", "DUMP", "EMPTY_BLOB", "EMPTY_CLOB", "EXP", "EXTRACT", "FIRST_VALUE", "FLOOR", "FROM_TZ", "GREATEST", - "GROUP_ID", "HEXTORAW", "INITCAP", "INSTR", "INSTR2", "INSTR4", "INSTRB", "INSTRC", "LAG", "LAST_DAY", "LAST_VALUE", "LEAD", "LEAST", "LENGTH", "LENGTH2", "LENGTH4", - "LENGTHB", "LENGTHC", "LISTAGG", "LN", "LNNVL", "LOCALTIMESTAMP", "LOG", "LOWER", "LPAD", "LTRIM", "MAX", "MEDIAN", "MIN", "MOD", "MONTHS_BETWEEN", "NANVL", "NCHR", - "NEW_TIME", "NEXT_DAY", "NTH_VALUE", "NULLIF", "NUMTODSINTERVAL", "NUMTOYMINTERVAL", "NVL", "NVL2", "POWER", "RANK", "RAWTOHEX", "REGEXP_COUNT", "REGEXP_INSTR", - "REGEXP_REPLACE", "REGEXP_SUBSTR", "REMAINDER", "REPLACE", "ROUND", "ROWNUM", "RPAD", "RTRIM", "SESSIONTIMEZONE", "SIGN", "SIN", "SINH", - "SOUNDEX", "SQRT", "STDDEV", "SUBSTR", "SUM", "SYS_CONTEXT", "SYSDATE", "SYSTIMESTAMP", "TAN", "TANH", "TO_CHAR", "TO_CLOB", "TO_DATE", "TO_DSINTERVAL", "TO_LOB", - "TO_MULTI_BYTE", "TO_NCLOB", "TO_NUMBER", "TO_SINGLE_BYTE", "TO_TIMESTAMP", "TO_TIMESTAMP_TZ", "TO_YMINTERVAL", "TRANSLATE", "TRIM", "TRUNC", "TZ_OFFSET", "UID", "UPPER", - "USER", "USERENV", "VAR_POP", "VAR_SAMP", "VARIANCE", "VSIZE " - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\\'[^\\\']*\\\'", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; - - langDef.mCaseSensitive = false; - langDef.mAutoIndentation = false; - - langDef.mName = "SQL"; - - inited = true; - } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::AngelScript() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const keywords[] = { - "and", "abstract", "auto", "bool", "break", "case", "cast", "class", "const", "continue", "default", "do", "double", "else", "enum", "false", "final", "float", "for", - "from", "funcdef", "function", "get", "if", "import", "in", "inout", "int", "interface", "int8", "int16", "int32", "int64", "is", "mixin", "namespace", "not", - "null", "or", "out", "override", "private", "protected", "return", "set", "shared", "super", "switch", "this ", "true", "typedef", "uint", "uint8", "uint16", "uint32", - "uint64", "void", "while", "xor" - }; - - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "cos", "sin", "tab", "acos", "asin", "atan", "atan2", "cosh", "sinh", "tanh", "log", "log10", "pow", "sqrt", "abs", "ceil", "floor", "fraction", "closeTo", "fpFromIEEE", "fpToIEEE", - "complex", "opEquals", "opAddAssign", "opSubAssign", "opMulAssign", "opDivAssign", "opAdd", "opSub", "opMul", "opDiv" - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; - - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; - - langDef.mName = "AngelScript"; - - inited = true; - } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Lua() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const keywords[] = { - "and", "break", "do", "", "else", "elseif", "end", "false", "for", "function", "if", "in", "", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while" - }; - - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "assert", "collectgarbage", "dofile", "error", "getmetatable", "ipairs", "loadfile", "load", "loadstring", "next", "pairs", "pcall", "print", "rawequal", "rawlen", "rawget", "rawset", - "select", "setmetatable", "tonumber", "tostring", "type", "xpcall", "_G", "_VERSION","arshift", "band", "bnot", "bor", "bxor", "btest", "extract", "lrotate", "lshift", "replace", - "rrotate", "rshift", "create", "resume", "running", "status", "wrap", "yield", "isyieldable", "debug","getuservalue", "gethook", "getinfo", "getlocal", "getregistry", "getmetatable", - "getupvalue", "upvaluejoin", "upvalueid", "setuservalue", "sethook", "setlocal", "setmetatable", "setupvalue", "traceback", "close", "flush", "input", "lines", "open", "output", "popen", - "read", "tmpfile", "type", "write", "close", "flush", "lines", "read", "seek", "setvbuf", "write", "__gc", "__tostring", "abs", "acos", "asin", "atan", "ceil", "cos", "deg", "exp", "tointeger", - "floor", "fmod", "ult", "log", "max", "min", "modf", "rad", "random", "randomseed", "sin", "sqrt", "string", "tan", "type", "atan2", "cosh", "sinh", "tanh", - "pow", "frexp", "ldexp", "log10", "pi", "huge", "maxinteger", "mininteger", "loadlib", "searchpath", "seeall", "preload", "cpath", "path", "searchers", "loaded", "module", "require", "clock", - "date", "difftime", "execute", "exit", "getenv", "remove", "rename", "setlocale", "time", "tmpname", "byte", "char", "dump", "find", "format", "gmatch", "gsub", "len", "lower", "match", "rep", - "reverse", "sub", "upper", "pack", "packsize", "unpack", "concat", "maxn", "insert", "pack", "unpack", "remove", "move", "sort", "offset", "codepoint", "char", "len", "codes", "charpattern", - "coroutine", "table", "io", "os", "string", "utf8", "bit32", "math", "debug", "package" - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\\'[^\\\']*\\\'", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - - langDef.mCommentStart = "--[["; - langDef.mCommentEnd = "]]"; - langDef.mSingleLineComment = "--"; - - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = false; - - langDef.mName = "Lua"; - - inited = true; - } - return langDef; -} \ No newline at end of file +const std::unordered_map TextEditor::OPEN_TO_CLOSE_CHAR = { + {'{', '}'}, + {'(' , ')'}, + {'[' , ']'} +}; +const std::unordered_map TextEditor::CLOSE_TO_OPEN_CHAR = { + {'}', '{'}, + {')' , '('}, + {']' , '['} +}; + +TextEditor::PaletteId TextEditor::defaultPalette = TextEditor::PaletteId::Dark; \ No newline at end of file diff --git a/Amalgam/include/ImGui/TextEditor.h b/Amalgam/include/ImGui/TextEditor.h index 692edb30..a5fbc646 100644 --- a/Amalgam/include/ImGui/TextEditor.h +++ b/Amalgam/include/ImGui/TextEditor.h @@ -1,5 +1,8 @@ #pragma once +#include +#include +#include #include #include #include @@ -7,12 +10,24 @@ #include #include #include -#include #include "imgui.h" -class TextEditor +#pragma warning (push) +#pragma warning( disable : 4267 ) +#pragma warning( disable : 4244 ) + +class IMGUI_API TextEditor { public: + // ------------- Exposed API ------------- // + + TextEditor(); + ~TextEditor(); + + enum class PaletteId + { + Dark, Light, Mariana, RetroBlue + }; enum class PaletteIndex { Default, @@ -31,6 +46,7 @@ public: Cursor, Selection, ErrorMarker, + ControlCharacter, Breakpoint, LineNumber, CurrentLineFill, @@ -38,25 +54,106 @@ public: CurrentLineEdge, Max }; - - enum class SelectionMode + enum class LanguageDefinitionId { - Normal, - Word, - Line + None, Cpp, C, Cs, Python, Lua, Json, Sql, AngelScript, Glsl, Hlsl + }; + enum class SetViewAtLineMode + { + FirstVisibleLine, Centered, LastVisibleLine }; - struct Breakpoint - { - int mLine; - bool mEnabled; - std::string mCondition; + inline void SetReadOnlyEnabled(bool aValue) { mReadOnly = aValue; } + inline bool IsReadOnlyEnabled() const { return mReadOnly; } + inline void SetAutoIndentEnabled(bool aValue) { mAutoIndent = aValue; } + inline bool IsAutoIndentEnabled() const { return mAutoIndent; } + inline void SetShowWhitespacesEnabled(bool aValue) { mShowWhitespaces = aValue; } + inline bool IsShowWhitespacesEnabled() const { return mShowWhitespaces; } + inline void SetShowLineNumbersEnabled(bool aValue) { mShowLineNumbers = aValue; } + inline bool IsShowLineNumbersEnabled() const { return mShowLineNumbers; } + inline void SetShortTabsEnabled(bool aValue) { mShortTabs = aValue; } + inline bool IsShortTabsEnabled() const { return mShortTabs; } + inline int GetLineCount() const { return mLines.size(); } + void SetPalette(PaletteId aValue); + PaletteId GetPalette() const { return mPaletteId; } + void SetPaletteIndex(PaletteIndex iIndex, ImU32 aValue); + ImU32 GetPaletteIndex(PaletteIndex iIndex) const { return mPalette[int(iIndex)]; } + void SetLanguageDefinition(LanguageDefinitionId aValue); + LanguageDefinitionId GetLanguageDefinition() const { return mLanguageDefinitionId; }; + const char* GetLanguageDefinitionName() const; + void SetTabSize(int aValue); + inline int GetTabSize() const { return mTabSize; } + void SetLineSpacing(float aValue); + inline float GetLineSpacing() const { return mLineSpacing; } - Breakpoint() - : mLine(-1) - , mEnabled(false) - {} - }; + inline static void SetDefaultPalette(PaletteId aValue) { defaultPalette = aValue; } + inline static PaletteId GetDefaultPalette() { return defaultPalette; } + + void SelectAll(); + void SelectLine(int aLine); + void SelectRegion(int aStartLine, int aStartChar, int aEndLine, int aEndChar); + void SelectNextOccurrenceOf(const char* aText, int aTextSize, bool aCaseSensitive = true); + void SelectAllOccurrencesOf(const char* aText, int aTextSize, bool aCaseSensitive = true); + bool AnyCursorHasSelection() const; + bool AllCursorsHaveSelection() const; + void ClearExtraCursors(); + void ClearSelections(); + void SetCursorPosition(int aLine, int aCharIndex); + inline void GetCursorPosition(int& outLine, int& outColumn) const + { + auto coords = GetSanitizedCursorCoordinates(); + outLine = coords.mLine; + outColumn = coords.mColumn; + } + int GetFirstVisibleLine(); + int GetLastVisibleLine(); + void SetViewAtLine(int aLine, SetViewAtLineMode aMode); + + void Copy(); + void Cut(); + void Paste(); + void Undo(int aSteps = 1); + void Redo(int aSteps = 1); + inline bool CanUndo() const { return !mReadOnly && mUndoIndex > 0; }; + inline bool CanRedo() const { return !mReadOnly && mUndoIndex < (int)mUndoBuffer.size(); }; + inline int GetUndoIndex() const { return mUndoIndex; }; + + void SetText(const std::string& aText); + std::string GetText() const; + + void SetTextLines(const std::vector& aLines); + std::vector GetTextLines() const; + + bool Render(const char* aTitle, bool aParentIsFocused = false, const ImVec2& aSize = ImVec2(), bool aBorder = false); + +private: + // ------------- Generic utils ------------- // + + static inline ImVec4 U32ColorToVec4(ImU32 in) + { + float s = 1.0f / 255.0f; + return ImVec4( + ((in >> IM_COL32_A_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_B_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_G_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_R_SHIFT) & 0xFF) * s); + } + static inline bool IsUTFSequence(char c) + { + return (c & 0xC0) == 0x80; + } + static inline float Distance(const ImVec2& a, const ImVec2& b) + { + float x = a.x - b.x; + float y = a.y - b.y; + return sqrt(x * x + y * y); + } + template + static inline T Max(T a, T b) { return a > b ? a : b; } + template + static inline T Min(T a, T b) { return a < b ? a : b; } + + // ------------- Internal ------------- // // Represents a character coordinate from the user's point of view, // i. e. consider an uniform grid (assuming fixed-width font) on the @@ -117,6 +214,35 @@ public: return mLine > o.mLine; return mColumn >= o.mColumn; } + + Coordinates operator -(const Coordinates& o) + { + return Coordinates(mLine - o.mLine, mColumn - o.mColumn); + } + + Coordinates operator +(const Coordinates& o) + { + return Coordinates(mLine + o.mLine, mColumn + o.mColumn); + } + }; + + struct Cursor + { + Coordinates mInteractiveStart = { 0, 0 }; + Coordinates mInteractiveEnd = { 0, 0 }; + inline Coordinates GetSelectionStart() const { return mInteractiveStart < mInteractiveEnd ? mInteractiveStart : mInteractiveEnd; } + inline Coordinates GetSelectionEnd() const { return mInteractiveStart > mInteractiveEnd ? mInteractiveStart : mInteractiveEnd; } + inline bool HasSelection() const { return mInteractiveStart != mInteractiveEnd; } + }; + + struct EditorState // state to be restored with undo/redo + { + int mCurrentCursor = 0; + int mLastAddedCursor = 0; + std::vector mCursors = { {{0,0}} }; + void AddCursor(); + int GetLastAddedCursorIndex(); + void SortCursorsFromTopToBottom(); }; struct Identifier @@ -125,157 +251,61 @@ public: std::string mDeclaration; }; - typedef std::string String; typedef std::unordered_map Identifiers; - typedef std::unordered_set Keywords; - typedef std::map ErrorMarkers; - typedef std::unordered_set Breakpoints; typedef std::array Palette; - typedef uint8_t Char; struct Glyph { - Char mChar; + char mChar; PaletteIndex mColorIndex = PaletteIndex::Default; bool mComment : 1; bool mMultiLineComment : 1; bool mPreprocessor : 1; - Glyph(Char aChar, PaletteIndex aColorIndex) : mChar(aChar), mColorIndex(aColorIndex), - mComment(false), mMultiLineComment(false), mPreprocessor(false) {} + Glyph(char aChar, PaletteIndex aColorIndex) : mChar(aChar), mColorIndex(aColorIndex), + mComment(false), mMultiLineComment(false), mPreprocessor(false) { + } }; typedef std::vector Line; - typedef std::vector Lines; struct LanguageDefinition { typedef std::pair TokenRegexString; - typedef std::vector TokenRegexStrings; - typedef bool(*TokenizeCallback)(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end, PaletteIndex & paletteIndex); + typedef bool(*TokenizeCallback)(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end, PaletteIndex& paletteIndex); std::string mName; - Keywords mKeywords; + std::unordered_set mKeywords; Identifiers mIdentifiers; Identifiers mPreprocIdentifiers; std::string mCommentStart, mCommentEnd, mSingleLineComment; - char mPreprocChar; - bool mAutoIndentation; + char mPreprocChar = '#'; + TokenizeCallback mTokenize = nullptr; + std::vector mTokenRegexStrings; + bool mCaseSensitive = true; - TokenizeCallback mTokenize; - - TokenRegexStrings mTokenRegexStrings; - - bool mCaseSensitive; - - LanguageDefinition() - : mPreprocChar('#'), mAutoIndentation(true), mTokenize(nullptr), mCaseSensitive(true) - { - } - - static const LanguageDefinition& CPlusPlus(); - static const LanguageDefinition& HLSL(); - static const LanguageDefinition& GLSL(); + static const LanguageDefinition& Cpp(); + static const LanguageDefinition& Hlsl(); + static const LanguageDefinition& Glsl(); + static const LanguageDefinition& Python(); static const LanguageDefinition& C(); - static const LanguageDefinition& SQL(); + static const LanguageDefinition& Sql(); static const LanguageDefinition& AngelScript(); static const LanguageDefinition& Lua(); + static const LanguageDefinition& Cs(); + static const LanguageDefinition& Json(); }; - TextEditor(); - ~TextEditor(); - - void SetLanguageDefinition(const LanguageDefinition& aLanguageDef); - const LanguageDefinition& GetLanguageDefinition() const { return mLanguageDefinition; } - - const Palette& GetPalette() const { return mPaletteBase; } - void SetPalette(const Palette& aValue); - - void SetErrorMarkers(const ErrorMarkers& aMarkers) { mErrorMarkers = aMarkers; } - void SetBreakpoints(const Breakpoints& aMarkers) { mBreakpoints = aMarkers; } - - void Render(const char* aTitle, const ImVec2& aSize = ImVec2(), bool aBorder = false); - void SetText(const std::string& aText); - std::string GetText() const; - - void SetTextLines(const std::vector& aLines); - std::vector GetTextLines() const; - - std::string GetSelectedText() const; - std::string GetCurrentLineText()const; - - int GetTotalLines() const { return (int)mLines.size(); } - bool IsOverwrite() const { return mOverwrite; } - - void SetReadOnly(bool aValue); - bool IsReadOnly() const { return mReadOnly; } - bool IsTextChanged() const { return mTextChanged; } - bool IsCursorPositionChanged() const { return mCursorPositionChanged; } - - bool IsColorizerEnabled() const { return mColorizerEnabled; } - void SetColorizerEnable(bool aValue); - - Coordinates GetCursorPosition() const { return GetActualCursorCoordinates(); } - void SetCursorPosition(const Coordinates& aPosition); - - inline void SetHandleMouseInputs (bool aValue){ mHandleMouseInputs = aValue;} - inline bool IsHandleMouseInputsEnabled() const { return mHandleKeyboardInputs; } - - inline void SetHandleKeyboardInputs (bool aValue){ mHandleKeyboardInputs = aValue;} - inline bool IsHandleKeyboardInputsEnabled() const { return mHandleKeyboardInputs; } - - inline void SetImGuiChildIgnored (bool aValue){ mIgnoreImGuiChild = aValue;} - inline bool IsImGuiChildIgnored() const { return mIgnoreImGuiChild; } - - inline void SetShowWhitespaces(bool aValue) { mShowWhitespaces = aValue; } - inline bool IsShowingWhitespaces() const { return mShowWhitespaces; } - - void SetTabSize(int aValue); - inline int GetTabSize() const { return mTabSize; } - - void InsertText(const std::string& aValue); - void InsertText(const char* aValue); - - void MoveUp(int aAmount = 1, bool aSelect = false); - void MoveDown(int aAmount = 1, bool aSelect = false); - void MoveLeft(int aAmount = 1, bool aSelect = false, bool aWordMode = false); - void MoveRight(int aAmount = 1, bool aSelect = false, bool aWordMode = false); - void MoveTop(bool aSelect = false); - void MoveBottom(bool aSelect = false); - void MoveHome(bool aSelect = false); - void MoveEnd(bool aSelect = false); - - void SetSelectionStart(const Coordinates& aPosition); - void SetSelectionEnd(const Coordinates& aPosition); - void SetSelection(const Coordinates& aStart, const Coordinates& aEnd, SelectionMode aMode = SelectionMode::Normal); - void SelectWordUnderCursor(); - void SelectAll(); - bool HasSelection() const; - - void Copy(); - void Cut(); - void Paste(); - void Delete(); - - bool CanUndo() const; - bool CanRedo() const; - void Undo(int aSteps = 1); - void Redo(int aSteps = 1); - - static const Palette& GetDarkPalette(); - static const Palette& GetLightPalette(); - static const Palette& GetRetroBluePalette(); - -private: - typedef std::vector> RegexList; - - struct EditorState + enum class UndoOperationType { Add, Delete }; + struct UndoOperation { - Coordinates mSelectionStart; - Coordinates mSelectionEnd; - Coordinates mCursorPosition; + std::string mText; + TextEditor::Coordinates mStart; + TextEditor::Coordinates mEnd; + UndoOperationType mType; }; + class UndoRecord { public: @@ -283,107 +313,164 @@ private: ~UndoRecord() {} UndoRecord( - const std::string& aAdded, - const TextEditor::Coordinates aAddedStart, - const TextEditor::Coordinates aAddedEnd, - - const std::string& aRemoved, - const TextEditor::Coordinates aRemovedStart, - const TextEditor::Coordinates aRemovedEnd, - + const std::vector& aOperations, TextEditor::EditorState& aBefore, TextEditor::EditorState& aAfter); void Undo(TextEditor* aEditor); void Redo(TextEditor* aEditor); - std::string mAdded; - Coordinates mAddedStart; - Coordinates mAddedEnd; - - std::string mRemoved; - Coordinates mRemovedStart; - Coordinates mRemovedEnd; + std::vector mOperations; EditorState mBefore; EditorState mAfter; }; - typedef std::vector UndoBuffer; + std::string GetText(const Coordinates& aStart, const Coordinates& aEnd) const; + std::string GetClipboardText() const; + std::string GetSelectedText(int aCursor = -1) const; + + void SetCursorPosition(const Coordinates& aPosition, int aCursor = -1, bool aClearSelection = true); + + int InsertTextAt(Coordinates& aWhere, const char* aValue); + void InsertTextAtCursor(const char* aValue, int aCursor = -1); + + enum class MoveDirection { Right = 0, Left = 1, Up = 2, Down = 3 }; + bool Move(int& aLine, int& aCharIndex, bool aLeft = false, bool aLockLine = false) const; + void MoveCharIndexAndColumn(int aLine, int& aCharIndex, int& aColumn) const; + void MoveCoords(Coordinates& aCoords, MoveDirection aDirection, bool aWordMode = false, int aLineCount = 1) const; + + void MoveUp(int aAmount = 1, bool aSelect = false); + void MoveDown(int aAmount = 1, bool aSelect = false); + void MoveLeft(bool aSelect = false, bool aWordMode = false); + void MoveRight(bool aSelect = false, bool aWordMode = false); + void MoveTop(bool aSelect = false); + void MoveBottom(bool aSelect = false); + void MoveHome(bool aSelect = false); + void MoveEnd(bool aSelect = false); + void EnterCharacter(ImWchar aChar, bool aShift); + void Backspace(bool aWordMode = false); + void Delete(bool aWordMode = false, const EditorState* aEditorState = nullptr); + + void SetSelection(Coordinates aStart, Coordinates aEnd, int aCursor = -1); + void SetSelection(int aStartLine, int aStartChar, int aEndLine, int aEndChar, int aCursor = -1); + + void SelectNextOccurrenceOf(const char* aText, int aTextSize, int aCursor = -1, bool aCaseSensitive = true); + void AddCursorForNextOccurrence(bool aCaseSensitive = true); + bool FindNextOccurrence(const char* aText, int aTextSize, const Coordinates& aFrom, Coordinates& outStart, Coordinates& outEnd, bool aCaseSensitive = true); + bool FindMatchingBracket(int aLine, int aCharIndex, Coordinates& out); + void ChangeCurrentLinesIndentation(bool aIncrease); + void MoveUpCurrentLines(); + void MoveDownCurrentLines(); + void ToggleLineComment(); + void RemoveCurrentLines(); + + float TextDistanceToLineStart(const Coordinates& aFrom, bool aSanitizeCoords = true) const; + void EnsureCursorVisible(int aCursor = -1, bool aStartToo = false); + + Coordinates SanitizeCoordinates(const Coordinates& aValue) const; + Coordinates GetSanitizedCursorCoordinates(int aCursor = -1, bool aStart = false) const; + Coordinates ScreenPosToCoordinates(const ImVec2& aPosition, bool* isOverLineNumber = nullptr) const; + Coordinates FindWordStart(const Coordinates& aFrom) const; + Coordinates FindWordEnd(const Coordinates& aFrom) const; + int GetCharacterIndexL(const Coordinates& aCoordinates) const; + int GetCharacterIndexR(const Coordinates& aCoordinates) const; + int GetCharacterColumn(int aLine, int aIndex) const; + int GetFirstVisibleCharacterIndex(int aLine) const; + int GetLineMaxColumn(int aLine, int aLimit = -1) const; + + Line& InsertLine(int aIndex); + void RemoveLine(int aIndex, const std::unordered_set* aHandledCursors = nullptr); + void RemoveLines(int aStart, int aEnd); + void DeleteRange(const Coordinates& aStart, const Coordinates& aEnd); + void DeleteSelection(int aCursor = -1); + + void RemoveGlyphsFromLine(int aLine, int aStartChar, int aEndChar = -1); + void AddGlyphsToLine(int aLine, int aTargetIndex, Line::iterator aSourceStart, Line::iterator aSourceEnd); + void AddGlyphToLine(int aLine, int aTargetIndex, Glyph aGlyph); + ImU32 GetGlyphColor(const Glyph& aGlyph) const; + + void HandleKeyboardInputs(bool aParentIsFocused = false); + void HandleMouseInputs(); + void UpdateViewVariables(float aScrollX, float aScrollY); + void Render(bool aParentIsFocused = false); + + void OnCursorPositionChanged(); + void OnLineChanged(bool aBeforeChange, int aLine, int aColumn, int aCharCount, bool aDeleted); + void MergeCursorsIfPossible(); + + void AddUndo(UndoRecord& aValue); - void ProcessInputs(); void Colorize(int aFromLine = 0, int aCount = -1); void ColorizeRange(int aFromLine = 0, int aToLine = 0); void ColorizeInternal(); - float TextDistanceToLineStart(const Coordinates& aFrom) const; - void EnsureCursorVisible(); - int GetPageSize() const; - std::string GetText(const Coordinates& aStart, const Coordinates& aEnd) const; - Coordinates GetActualCursorCoordinates() const; - Coordinates SanitizeCoordinates(const Coordinates& aValue) const; - void Advance(Coordinates& aCoordinates) const; - void DeleteRange(const Coordinates& aStart, const Coordinates& aEnd); - int InsertTextAt(Coordinates& aWhere, const char* aValue); - void AddUndo(UndoRecord& aValue); - Coordinates ScreenPosToCoordinates(const ImVec2& aPosition) const; - Coordinates FindWordStart(const Coordinates& aFrom) const; - Coordinates FindWordEnd(const Coordinates& aFrom) const; - Coordinates FindNextWord(const Coordinates& aFrom) const; - int GetCharacterIndex(const Coordinates& aCoordinates) const; - int GetCharacterColumn(int aLine, int aIndex) const; - int GetLineCharacterCount(int aLine) const; - int GetLineMaxColumn(int aLine) const; - bool IsOnWordBoundary(const Coordinates& aAt) const; - void RemoveLine(int aStart, int aEnd); - void RemoveLine(int aIndex); - Line& InsertLine(int aIndex); - void EnterCharacter(ImWchar aChar, bool aShift); - void Backspace(); - void DeleteSelection(); - std::string GetWordUnderCursor() const; - std::string GetWordAt(const Coordinates& aCoords) const; - ImU32 GetGlyphColor(const Glyph& aGlyph) const; - void HandleKeyboardInputs(); - void HandleMouseInputs(); - void Render(); - - float mLineSpacing; - Lines mLines; + std::vector mLines; EditorState mState; - UndoBuffer mUndoBuffer; - int mUndoIndex; + std::vector mUndoBuffer; + int mUndoIndex = 0; - int mTabSize; - bool mOverwrite; - bool mReadOnly; - bool mWithinRender; - bool mScrollToCursor; - bool mScrollToTop; - bool mTextChanged; - bool mColorizerEnabled; - float mTextStart; // position (in pixels) where a code line starts relative to the left of the TextEditor. - int mLeftMargin; - bool mCursorPositionChanged; - int mColorRangeMin, mColorRangeMax; - SelectionMode mSelectionMode; - bool mHandleKeyboardInputs; - bool mHandleMouseInputs; - bool mIgnoreImGuiChild; - bool mShowWhitespaces; + int mTabSize = 4; + float mLineSpacing = 1.0f; + bool mReadOnly = false; + bool mAutoIndent = true; + bool mShowWhitespaces = true; + bool mShowLineNumbers = true; + bool mShortTabs = false; - Palette mPaletteBase; - Palette mPalette; - LanguageDefinition mLanguageDefinition; - RegexList mRegexList; + int mSetViewAtLine = -1; + SetViewAtLineMode mSetViewAtLineMode; + int mEnsureCursorVisible = -1; + bool mEnsureCursorVisibleStartToo = false; + bool mScrollToTop = false; - bool mCheckComments; - Breakpoints mBreakpoints; - ErrorMarkers mErrorMarkers; + float mTextStart = 20.0f; // position (in pixels) where a code line starts relative to the left of the TextEditor. + int mLeftMargin = 10; ImVec2 mCharAdvance; - Coordinates mInteractiveStart, mInteractiveEnd; - std::string mLineBuffer; - uint64_t mStartTime; + float mCurrentSpaceHeight = 20.0f; + float mCurrentSpaceWidth = 20.0f; + float mLastClickTime = -1.0f; + ImVec2 mLastClickPos; + int mFirstVisibleLine = 0; + int mLastVisibleLine = 0; + int mVisibleLineCount = 0; + int mFirstVisibleColumn = 0; + int mLastVisibleColumn = 0; + int mVisibleColumnCount = 0; + float mContentWidth = 0.0f; + float mContentHeight = 0.0f; + float mScrollX = 0.0f; + float mScrollY = 0.0f; + bool mPanning = false; + bool mDraggingSelection = false; + ImVec2 mLastMousePos; + bool mCursorPositionChanged = false; + bool mCursorOnBracket = false; + Coordinates mMatchingBracketCoords; - float mLastClick; -}; \ No newline at end of file + int mColorRangeMin = 0; + int mColorRangeMax = 0; + bool mCheckComments = true; + PaletteId mPaletteId; + Palette mPalette; + LanguageDefinitionId mLanguageDefinitionId; + const LanguageDefinition* mLanguageDefinition = nullptr; + + inline bool IsHorizontalScrollbarVisible() const { return mCurrentSpaceWidth > mContentWidth; } + inline bool IsVerticalScrollbarVisible() const { return mCurrentSpaceHeight > mContentHeight; } + inline int TabSizeAtColumn(int aColumn) const { return mTabSize - (aColumn % mTabSize); } + + static const Palette& GetDarkPalette(); + static const Palette& GetMarianaPalette(); + static const Palette& GetLightPalette(); + static const Palette& GetRetroBluePalette(); + static const std::unordered_map OPEN_TO_CLOSE_CHAR; + static const std::unordered_map CLOSE_TO_OPEN_CHAR; + static PaletteId defaultPalette; + +private: + struct RegexList; + std::shared_ptr mRegexList; +}; + +#pragma warning (pop) \ No newline at end of file diff --git a/Amalgam/include/ImGui/imconfig.h b/Amalgam/include/ImGui/imconfig.h index 63d6ba08..8f8bc3b9 100644 --- a/Amalgam/include/ImGui/imconfig.h +++ b/Amalgam/include/ImGui/imconfig.h @@ -29,7 +29,6 @@ //---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names. //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS -//#define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87+ disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This is automatically done by IMGUI_DISABLE_OBSOLETE_FUNCTIONS. //---- Disable all of Dear ImGui or don't implement standard windows/tools. // It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp. @@ -49,6 +48,7 @@ //#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) //#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. //#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). +//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded font (ProggyClean.ttf), remove ~9.5 KB from output binary. AddFontDefault() will assert. //#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available //---- Enable Test Engine / Automation features. @@ -59,9 +59,12 @@ //#define IMGUI_INCLUDE_IMGUI_USER_H //#define IMGUI_USER_H_FILENAME "my_folder/my_imgui_user.h" -//---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another) +//---- Pack vertex colors as BGRA8 instead of RGBA8 (to avoid converting from one to another). Need dedicated backend support. //#define IMGUI_USE_BGRA_PACKED_COLOR +//---- Use legacy CRC32-adler tables (used before 1.91.6), in order to preserve old .ini data that you cannot afford to invalidate. +//#define IMGUI_USE_LEGACY_CRC32_ADLER + //---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...) //#define IMGUI_USE_WCHAR32 diff --git a/Amalgam/include/ImGui/imgui.cpp b/Amalgam/include/ImGui/imgui.cpp index f85d4f13..5ca57dfc 100644 --- a/Amalgam/include/ImGui/imgui.cpp +++ b/Amalgam/include/ImGui/imgui.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.91.4 +// dear imgui, v1.91.8 // (main code and documentation) // Help: @@ -25,7 +25,7 @@ // please post in https://github.com/ocornut/imgui/discussions if you cannot find a solution in resources above. // Everything else should be asked in 'Issues'! We are building a database of cross-linked knowledge there. -// Copyright (c) 2014-2024 Omar Cornut +// Copyright (c) 2014-2025 Omar Cornut // Developed by Omar Cornut and every direct or indirect contributors to the GitHub. // See LICENSE.txt for copyright and licensing details (standard MIT License). // This library is free but needs your support to sustain development and maintenance. @@ -85,6 +85,7 @@ CODE // [SECTION] SCROLLING // [SECTION] TOOLTIPS // [SECTION] POPUPS +// [SECTION] WINDOW FOCUS // [SECTION] KEYBOARD/GAMEPAD NAVIGATION // [SECTION] DRAG AND DROP // [SECTION] LOGGING/CAPTURING @@ -174,7 +175,6 @@ CODE - Set 'io.BackendFlags |= ImGuiBackendFlags_HasGamepad' + call io.AddKeyEvent/AddKeyAnalogEvent() with ImGuiKey_Gamepad_XXX keys. - For analog values (0.0f to 1.0f), backend is responsible to handling a dead-zone and rescaling inputs accordingly. Backend code will probably need to transform your raw inputs (such as e.g. remapping your 0.2..0.9 raw input range to 0.0..1.0 imgui range, etc.). - - BEFORE 1.87, BACKENDS USED TO WRITE TO io.NavInputs[]. This is now obsolete. Please call io functions instead! - If you need to share inputs between your game and the Dear ImGui interface, the easiest approach is to go all-or-nothing, with a buttons combo to toggle the target. Please reach out if you think the game vs navigation input sharing could be improved. @@ -430,6 +430,25 @@ CODE When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. + - 2025/01/22 (1.91.8) - removed ImGuiColorEditFlags_AlphaPreview (made value 0): it is now the default behavior. + prior to 1.91.8: alpha was made opaque in the preview by default _unless_ using ImGuiColorEditFlags_AlphaPreview. We now display the preview as transparent by default. You can use ImGuiColorEditFlags_AlphaOpaque to use old behavior. + the new flags (ImGuiColorEditFlags_AlphaOpaque, ImGuiColorEditFlags_AlphaNoBg + existing ImGuiColorEditFlags_AlphaPreviewHalf) may be combined better and allow finer controls: + - 2025/01/14 (1.91.7) - renamed ImGuiTreeNodeFlags_SpanTextWidth to ImGuiTreeNodeFlags_SpanLabelWidth for consistency with other names. Kept redirection enum (will obsolete). (#6937) + - 2024/11/27 (1.91.6) - changed CRC32 table from CRC32-adler to CRC32c polynomial in order to be compatible with the result of SSE 4.2 instructions. + As a result, old .ini data may be partially lost (docking and tables information particularly). + Because some users have crafted and storing .ini data as a way to workaround limitations of the docking API, we are providing a '#define IMGUI_USE_LEGACY_CRC32_ADLER' compile-time option to keep using old CRC32 tables if you cannot afford invalidating old .ini data. + - 2024/11/06 (1.91.5) - commented/obsoleted out pre-1.87 IO system (equivalent to using IMGUI_DISABLE_OBSOLETE_KEYIO or IMGUI_DISABLE_OBSOLETE_FUNCTIONS before) + - io.KeyMap[] and io.KeysDown[] are removed (obsoleted February 2022). + - io.NavInputs[] and ImGuiNavInput are removed (obsoleted July 2022). + - pre-1.87 backends are not supported: + - backends need to call io.AddKeyEvent(), io.AddMouseEvent() instead of writing to io.KeysDown[], io.MouseDown[] fields. + - backends need to call io.AddKeyAnalogEvent() for gamepad values instead of writing to io.NavInputs[] fields. + - for more reference: + - read 1.87 and 1.88 part of this section or read Changelog for 1.87 and 1.88. + - read https://github.com/ocornut/imgui/issues/4921 + - if you have trouble updating a very old codebase using legacy backend-specific key codes: consider updating to 1.91.4 first, then #define IMGUI_DISABLE_OBSOLETE_KEYIO, then update to latest. + - obsoleted ImGuiKey_COUNT (it is unusually error-prone/misleading since valid keys don't start at 0). probably use ImGuiKey_NamedKey_BEGIN/ImGuiKey_NamedKey_END? + - fonts: removed const qualifiers from most font functions in prevision for upcoming font improvements. - 2024/10/18 (1.91.4) - renamed ImGuiCol_NavHighlight to ImGuiCol_NavCursor (for consistency with newly exposed and reworked features). Kept inline redirection enum (will obsolete). - 2024/10/14 (1.91.4) - moved ImGuiConfigFlags_NavEnableSetMousePos to standalone io.ConfigNavMoveSetMousePos bool. moved ImGuiConfigFlags_NavNoCaptureKeyboard to standalone io.ConfigNavCaptureKeyboard bool (note the inverted value!). @@ -632,7 +651,7 @@ CODE - Backend writing to io.MouseHoveredViewport -> backend should call io.AddMouseViewportEvent() [Docking branch w/ multi-viewports only] note: for all calls to IO new functions, the Dear ImGui context should be bound/current. read https://github.com/ocornut/imgui/issues/4921 for details. - - 2022/01/10 (1.87) - inputs: reworked keyboard IO. Removed io.KeyMap[], io.KeysDown[] in favor of calling io.AddKeyEvent(). Removed GetKeyIndex(), now unnecessary. All IsKeyXXX() functions now take ImGuiKey values. All features are still functional until IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Read Changelog and Release Notes for details. + - 2022/01/10 (1.87) - inputs: reworked keyboard IO. Removed io.KeyMap[], io.KeysDown[] in favor of calling io.AddKeyEvent(), ImGui::IsKeyDown(). Removed GetKeyIndex(), now unnecessary. All IsKeyXXX() functions now take ImGuiKey values. All features are still functional until IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Read Changelog and Release Notes for details. - IsKeyPressed(MY_NATIVE_KEY_XXX) -> use IsKeyPressed(ImGuiKey_XXX) - IsKeyPressed(GetKeyIndex(ImGuiKey_XXX)) -> use IsKeyPressed(ImGuiKey_XXX) - Backend writing to io.KeyMap[],io.KeysDown[] -> backend should call io.AddKeyEvent() (+ call io.SetKeyEventNativeData() if you want legacy user code to stil function with legacy key codes). @@ -1081,7 +1100,7 @@ CODE #else #include #endif -#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP || WINAPI_FAMILY == WINAPI_FAMILY_GAMES) +#if defined(WINAPI_FAMILY) && ((defined(WINAPI_FAMILY_APP) && WINAPI_FAMILY == WINAPI_FAMILY_APP) || (defined(WINAPI_FAMILY_GAMES) && WINAPI_FAMILY == WINAPI_FAMILY_GAMES)) // The UWP and GDK Win32 API subsets don't support clipboard nor IME functions #define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS #define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS @@ -1124,17 +1143,20 @@ CODE #pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access +#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type #elif defined(__GNUC__) // We disable -Wpragmas because GCC doesn't provide a has_warning equivalent and some forks/patches may not follow the warning/version association. -#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind -#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used -#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size -#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*' -#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function -#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value -#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked -#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false -#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size +#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe +#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'int'/'void*', but argument X has type 'unsigned int'/'ImGuiWindow*' +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers #endif // Debug options @@ -1185,6 +1207,10 @@ namespace ImGui // Item static void ItemHandleShortcut(ImGuiID id); +// Window Focus +static int FindWindowFocusIndex(ImGuiWindow* window); +static void UpdateWindowInFocusOrderList(ImGuiWindow* window, bool just_created, ImGuiWindowFlags new_flags); + // Navigation static void NavUpdate(); static void NavUpdateWindowing(); @@ -1205,14 +1231,15 @@ static ImVec2 NavCalcPreferredRefPos(); static void NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window); static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window); static void NavRestoreLayer(ImGuiNavLayer layer); -static int FindWindowFocusIndex(ImGuiWindow* window); // Error Checking and Debug Tools static void ErrorCheckNewFrameSanityChecks(); static void ErrorCheckEndFrameSanityChecks(); +#ifndef IMGUI_DISABLE_DEBUG_TOOLS static void UpdateDebugToolItemPicker(); static void UpdateDebugToolStackQueries(); static void UpdateDebugToolFlashStyleColor(); +#endif // Inputs static void UpdateKeyboardInputs(); @@ -1229,6 +1256,7 @@ static void RenderWindowTitleBarContents(ImGuiWindow* window, const static void RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 col); static void RenderDimmedBackgrounds(); static void SetLastItemDataForWindow(ImGuiWindow* window, const ImRect& rect); +static void SetLastItemDataForChildWindowItem(ImGuiWindow* window, const ImRect& rect); // Viewports const ImGuiID IMGUI_VIEWPORT_DEFAULT_ID = 0x11111111; // Using an arbitrary constant instead of e.g. ImHashStr("ViewportDefault", 0); so it's easier to spot in the debugger. The exact value doesn't matter. @@ -1310,11 +1338,11 @@ ImGuiStyle::ImGuiStyle() GrabMinSize = 12.0f; // Minimum width/height of a grab box for slider/scrollbar GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. LogSliderDeadzone = 4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero. - TabRounding = 4.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. + TabRounding = 5.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. TabBorderSize = 0.0f; // Thickness of border around tabs. TabMinWidthForCloseButton = 0.0f; // Minimum width for close button to appear on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected. TabBarBorderSize = 1.0f; // Thickness of tab-bar separator, which takes on the tab active color to denote focus. - TabBarOverlineSize = 2.0f; // Thickness of tab-bar overline, which highlights the selected tab-bar. + TabBarOverlineSize = 1.0f; // Thickness of tab-bar overline, which highlights the selected tab-bar. TableAngledHeadersAngle = 35.0f * (IM_PI / 180.0f); // Angle of angled headers (supported values range from -50 degrees to +50 degrees). TableAngledHeadersTextAlign = ImVec2(0.5f,0.0f);// Alignment of angled headers within the cell ColorButtonPosition = ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. @@ -1388,10 +1416,6 @@ ImGuiIO::ImGuiIO() IniSavingRate = 5.0f; IniFilename = "imgui.ini"; // Important: "imgui.ini" is relative to current working dir, most apps will want to lock this to an absolute path (e.g. same path as executables). LogFilename = "imgui_log.txt"; -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - for (int i = 0; i < ImGuiKey_COUNT; i++) - KeyMap[i] = -1; -#endif UserData = NULL; Fonts = NULL; @@ -1422,6 +1446,7 @@ ImGuiIO::ImGuiIO() ConfigDragClickToInputText = false; ConfigWindowsResizeFromEdges = true; ConfigWindowsMoveFromTitleBarOnly = false; + ConfigWindowsCopyContentsWithCtrlC = false; ConfigScrollbarScrollByPage = true; ConfigMemoryCompactTimer = 60.0f; ConfigDebugIsDebuggerPresent = false; @@ -1453,8 +1478,6 @@ ImGuiIO::ImGuiIO() for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f; for (int i = 0; i < IM_ARRAYSIZE(KeysData); i++) { KeysData[i].DownDuration = KeysData[i].DownDurationPrev = -1.0f; } AppAcceptingEvents = true; - BackendUsingLegacyKeyArrays = (ImS8)-1; - BackendUsingLegacyNavInputArray = true; // assume using legacy array until proven wrong } // Pass in translated ASCII characters for text input. @@ -1535,16 +1558,15 @@ void ImGuiIO::ClearEventsQueue() // Clear current keyboard/gamepad state + current frame text input buffer. Equivalent to releasing all keys/buttons. void ImGuiIO::ClearInputKeys() { -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - memset(KeysDown, 0, sizeof(KeysDown)); -#endif - for (int n = 0; n < IM_ARRAYSIZE(KeysData); n++) + ImGuiContext& g = *Ctx; + for (int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key++) { - if (ImGui::IsMouseKey((ImGuiKey)(n + ImGuiKey_KeysData_OFFSET))) + if (ImGui::IsMouseKey((ImGuiKey)key)) continue; - KeysData[n].Down = false; - KeysData[n].DownDuration = -1.0f; - KeysData[n].DownDurationPrev = -1.0f; + ImGuiKeyData* key_data = &g.IO.KeysData[key - ImGuiKey_NamedKey_BEGIN]; + key_data->Down = false; + key_data->DownDuration = -1.0f; + key_data->DownDurationPrev = -1.0f; } KeyCtrl = KeyShift = KeyAlt = KeySuper = false; KeyMods = ImGuiMod_None; @@ -1555,7 +1577,7 @@ void ImGuiIO::ClearInputMouse() { for (ImGuiKey key = ImGuiKey_Mouse_BEGIN; key < ImGuiKey_Mouse_END; key = (ImGuiKey)(key + 1)) { - ImGuiKeyData* key_data = &KeysData[key - ImGuiKey_KeysData_OFFSET]; + ImGuiKeyData* key_data = &KeysData[key - ImGuiKey_NamedKey_BEGIN]; key_data->Down = false; key_data->DownDuration = -1.0f; key_data->DownDurationPrev = -1.0f; @@ -1622,17 +1644,6 @@ void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value) else if (key == ImGuiKey_RightCtrl) { key = ImGuiKey_RightSuper; } } - // Verify that backend isn't mixing up using new io.AddKeyEvent() api and old io.KeysDown[] + io.KeyMap[] data. -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - IM_ASSERT((BackendUsingLegacyKeyArrays == -1 || BackendUsingLegacyKeyArrays == 0) && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!"); - if (BackendUsingLegacyKeyArrays == -1) - for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_NamedKey_END; n++) - IM_ASSERT(KeyMap[n] == -1 && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!"); - BackendUsingLegacyKeyArrays = 0; -#endif - if (ImGui::IsGamepadKey(key)) - BackendUsingLegacyNavInputArray = false; - // Filter duplicate (in particular: key mods and gamepad analog values are commonly spammed) const ImGuiInputEvent* latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_Key, (int)key); const ImGuiKeyData* key_data = ImGui::GetKeyData(&g, key); @@ -1668,20 +1679,10 @@ void ImGuiIO::SetKeyEventNativeData(ImGuiKey key, int native_keycode, int native return; IM_ASSERT(ImGui::IsNamedKey(key)); // >= 512 IM_ASSERT(native_legacy_index == -1 || ImGui::IsLegacyKey((ImGuiKey)native_legacy_index)); // >= 0 && <= 511 - IM_UNUSED(native_keycode); // Yet unused - IM_UNUSED(native_scancode); // Yet unused - - // Build native->imgui map so old user code can still call key functions with native 0..511 values. -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - const int legacy_key = (native_legacy_index != -1) ? native_legacy_index : native_keycode; - if (!ImGui::IsLegacyKey((ImGuiKey)legacy_key)) - return; - KeyMap[legacy_key] = key; - KeyMap[key] = legacy_key; -#else - IM_UNUSED(key); - IM_UNUSED(native_legacy_index); -#endif + IM_UNUSED(key); // Yet unused + IM_UNUSED(native_keycode); // Yet unused + IM_UNUSED(native_scancode); // Yet unused + IM_UNUSED(native_legacy_index); // Yet unused } // Set master flag for accepting key/mouse/text events (default to true). Useful if you have native dialog boxes that are interrupting your application loop/refresh, and you want to disable events being queued while your app is frozen. @@ -2162,11 +2163,14 @@ void ImFormatStringToTempBufferV(const char** out_buf, const char** out_buf_end, } } +#ifndef IMGUI_ENABLE_SSE4_2_CRC // CRC32 needs a 1KB lookup table (not cache friendly) // Although the code to generate the table is simple and shorter than the table itself, using a const table allows us to easily: // - avoid an unnecessary branch/memory tap, - keep the ImHashXXX functions usable by static constructors, - make it thread-safe. static const ImU32 GCrc32LookupTable[256] = { +#ifdef IMGUI_USE_LEGACY_CRC32_ADLER + // Legacy CRC32-adler table used pre 1.91.6 (before 2024/11/27). Only use if you cannot afford invalidating old .ini data. 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91, 0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5, 0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59, @@ -2183,7 +2187,27 @@ static const ImU32 GCrc32LookupTable[256] = 0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45, 0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9, 0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF,0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D, +#else + // CRC32c table compatible with SSE 4.2 instructions + 0x00000000,0xF26B8303,0xE13B70F7,0x1350F3F4,0xC79A971F,0x35F1141C,0x26A1E7E8,0xD4CA64EB,0x8AD958CF,0x78B2DBCC,0x6BE22838,0x9989AB3B,0x4D43CFD0,0xBF284CD3,0xAC78BF27,0x5E133C24, + 0x105EC76F,0xE235446C,0xF165B798,0x030E349B,0xD7C45070,0x25AFD373,0x36FF2087,0xC494A384,0x9A879FA0,0x68EC1CA3,0x7BBCEF57,0x89D76C54,0x5D1D08BF,0xAF768BBC,0xBC267848,0x4E4DFB4B, + 0x20BD8EDE,0xD2D60DDD,0xC186FE29,0x33ED7D2A,0xE72719C1,0x154C9AC2,0x061C6936,0xF477EA35,0xAA64D611,0x580F5512,0x4B5FA6E6,0xB93425E5,0x6DFE410E,0x9F95C20D,0x8CC531F9,0x7EAEB2FA, + 0x30E349B1,0xC288CAB2,0xD1D83946,0x23B3BA45,0xF779DEAE,0x05125DAD,0x1642AE59,0xE4292D5A,0xBA3A117E,0x4851927D,0x5B016189,0xA96AE28A,0x7DA08661,0x8FCB0562,0x9C9BF696,0x6EF07595, + 0x417B1DBC,0xB3109EBF,0xA0406D4B,0x522BEE48,0x86E18AA3,0x748A09A0,0x67DAFA54,0x95B17957,0xCBA24573,0x39C9C670,0x2A993584,0xD8F2B687,0x0C38D26C,0xFE53516F,0xED03A29B,0x1F682198, + 0x5125DAD3,0xA34E59D0,0xB01EAA24,0x42752927,0x96BF4DCC,0x64D4CECF,0x77843D3B,0x85EFBE38,0xDBFC821C,0x2997011F,0x3AC7F2EB,0xC8AC71E8,0x1C661503,0xEE0D9600,0xFD5D65F4,0x0F36E6F7, + 0x61C69362,0x93AD1061,0x80FDE395,0x72966096,0xA65C047D,0x5437877E,0x4767748A,0xB50CF789,0xEB1FCBAD,0x197448AE,0x0A24BB5A,0xF84F3859,0x2C855CB2,0xDEEEDFB1,0xCDBE2C45,0x3FD5AF46, + 0x7198540D,0x83F3D70E,0x90A324FA,0x62C8A7F9,0xB602C312,0x44694011,0x5739B3E5,0xA55230E6,0xFB410CC2,0x092A8FC1,0x1A7A7C35,0xE811FF36,0x3CDB9BDD,0xCEB018DE,0xDDE0EB2A,0x2F8B6829, + 0x82F63B78,0x709DB87B,0x63CD4B8F,0x91A6C88C,0x456CAC67,0xB7072F64,0xA457DC90,0x563C5F93,0x082F63B7,0xFA44E0B4,0xE9141340,0x1B7F9043,0xCFB5F4A8,0x3DDE77AB,0x2E8E845F,0xDCE5075C, + 0x92A8FC17,0x60C37F14,0x73938CE0,0x81F80FE3,0x55326B08,0xA759E80B,0xB4091BFF,0x466298FC,0x1871A4D8,0xEA1A27DB,0xF94AD42F,0x0B21572C,0xDFEB33C7,0x2D80B0C4,0x3ED04330,0xCCBBC033, + 0xA24BB5A6,0x502036A5,0x4370C551,0xB11B4652,0x65D122B9,0x97BAA1BA,0x84EA524E,0x7681D14D,0x2892ED69,0xDAF96E6A,0xC9A99D9E,0x3BC21E9D,0xEF087A76,0x1D63F975,0x0E330A81,0xFC588982, + 0xB21572C9,0x407EF1CA,0x532E023E,0xA145813D,0x758FE5D6,0x87E466D5,0x94B49521,0x66DF1622,0x38CC2A06,0xCAA7A905,0xD9F75AF1,0x2B9CD9F2,0xFF56BD19,0x0D3D3E1A,0x1E6DCDEE,0xEC064EED, + 0xC38D26C4,0x31E6A5C7,0x22B65633,0xD0DDD530,0x0417B1DB,0xF67C32D8,0xE52CC12C,0x1747422F,0x49547E0B,0xBB3FFD08,0xA86F0EFC,0x5A048DFF,0x8ECEE914,0x7CA56A17,0x6FF599E3,0x9D9E1AE0, + 0xD3D3E1AB,0x21B862A8,0x32E8915C,0xC083125F,0x144976B4,0xE622F5B7,0xF5720643,0x07198540,0x590AB964,0xAB613A67,0xB831C993,0x4A5A4A90,0x9E902E7B,0x6CFBAD78,0x7FAB5E8C,0x8DC0DD8F, + 0xE330A81A,0x115B2B19,0x020BD8ED,0xF0605BEE,0x24AA3F05,0xD6C1BC06,0xC5914FF2,0x37FACCF1,0x69E9F0D5,0x9B8273D6,0x88D28022,0x7AB90321,0xAE7367CA,0x5C18E4C9,0x4F48173D,0xBD23943E, + 0xF36E6F75,0x0105EC76,0x12551F82,0xE03E9C81,0x34F4F86A,0xC69F7B69,0xD5CF889D,0x27A40B9E,0x79B737BA,0x8BDCB4B9,0x988C474D,0x6AE7C44E,0xBE2DA0A5,0x4C4623A6,0x5F16D052,0xAD7D5351 +#endif }; +#endif // Known size hash // It is ok to call ImHashData on a string with known length but the ### operator won't be supported. @@ -2192,10 +2216,22 @@ ImGuiID ImHashData(const void* data_p, size_t data_size, ImGuiID seed) { ImU32 crc = ~seed; const unsigned char* data = (const unsigned char*)data_p; + const unsigned char *data_end = (const unsigned char*)data_p + data_size; +#ifndef IMGUI_ENABLE_SSE4_2_CRC const ImU32* crc32_lut = GCrc32LookupTable; - while (data_size-- != 0) + while (data < data_end) crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *data++]; return ~crc; +#else + while (data + 4 <= data_end) + { + crc = _mm_crc32_u32(crc, *(ImU32*)data); + data += 4; + } + while (data < data_end) + crc = _mm_crc32_u8(crc, *data++); + return ~crc; +#endif } // Zero-terminated string hash, with support for ### to reset back to seed value @@ -2209,7 +2245,9 @@ ImGuiID ImHashStr(const char* data_p, size_t data_size, ImGuiID seed) seed = ~seed; ImU32 crc = seed; const unsigned char* data = (const unsigned char*)data_p; +#ifndef IMGUI_ENABLE_SSE4_2_CRC const ImU32* crc32_lut = GCrc32LookupTable; +#endif if (data_size != 0) { while (data_size-- != 0) @@ -2217,7 +2255,11 @@ ImGuiID ImHashStr(const char* data_p, size_t data_size, ImGuiID seed) unsigned char c = *data++; if (c == '#' && data_size >= 2 && data[0] == '#' && data[1] == '#') crc = seed; +#ifndef IMGUI_ENABLE_SSE4_2_CRC crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; +#else + crc = _mm_crc32_u8(crc, c); +#endif } } else @@ -2226,7 +2268,11 @@ ImGuiID ImHashStr(const char* data_p, size_t data_size, ImGuiID seed) { if (c == '#' && data[0] == '#' && data[1] == '#') crc = seed; +#ifndef IMGUI_ENABLE_SSE4_2_CRC crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; +#else + crc = _mm_crc32_u8(crc, c); +#endif } } return ~crc; @@ -2241,7 +2287,7 @@ ImGuiID ImHashStr(const char* data_p, size_t data_size, ImGuiID seed) ImFileHandle ImFileOpen(const char* filename, const char* mode) { -#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(__CYGWIN__) && !defined(__GNUC__) +#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && (defined(__MINGW32__) || (!defined(__CYGWIN__) && !defined(__GNUC__))) // We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames. // Previously we used ImTextCountCharsFromUtf8/ImTextStrFromUtf8 here but we now need to support ImWchar16 and ImWchar32! const int filename_wsize = ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0); @@ -3505,7 +3551,6 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) return "Unknown"; } - //----------------------------------------------------------------------------- // [SECTION] RENDER HELPERS // Some of those (internal) functions are currently quite a legacy mess - their signature and behavior will change, @@ -3637,7 +3682,7 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con // min max ellipsis_max // <-> this is generally some padding value - const ImFont* font = draw_list->_Data->Font; + ImFont* font = draw_list->_Data->Font; const float font_size = draw_list->_Data->FontSize; const float font_scale = draw_list->_Data->FontScale; const char* text_end_ellipsis = NULL; @@ -3847,7 +3892,8 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) Time = 0.0f; FrameCount = 0; FrameCountEnded = FrameCountRendered = -1; - WithinFrameScope = WithinFrameScopeWithImplicitWindow = WithinEndChild = false; + WithinEndChildID = 0; + WithinFrameScope = WithinFrameScopeWithImplicitWindow = false; GcCompactAll = false; TestEngineHookItems = false; TestEngine = NULL; @@ -3889,9 +3935,8 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) ActiveIdSource = ImGuiInputSource_None; ActiveIdMouseButton = -1; ActiveIdPreviousFrame = 0; - ActiveIdPreviousFrameIsAlive = false; - ActiveIdPreviousFrameHasBeenEditedBefore = false; - ActiveIdPreviousFrameWindow = NULL; + memset(&DeactivatedItemData, 0, sizeof(DeactivatedItemData)); + memset(&ActiveIdValueOnActivation, 0, sizeof(ActiveIdValueOnActivation)); LastActiveId = 0; LastActiveIdTimer = 0.0f; @@ -4007,7 +4052,8 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) memset(LocalizationTable, 0, sizeof(LocalizationTable)); LogEnabled = false; - LogType = ImGuiLogType_None; + LogFlags = ImGuiLogFlags_None; + LogWindow = NULL; LogNextPrefix = LogNextSuffix = NULL; LogFile = NULL; LogLinePosY = FLT_MAX; @@ -4132,7 +4178,7 @@ void ImGui::Shutdown() g.WindowsById.Clear(); g.NavWindow = NULL; g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL; - g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL; + g.ActiveIdWindow = NULL; g.MovingWindow = NULL; g.KeysRoutingTable.Clear(); @@ -4212,7 +4258,6 @@ void ImGui::CallContextHooks(ImGuiContext* ctx, ImGuiContextHookType hook_type) hook.Callback(&g, &hook); } - //----------------------------------------------------------------------------- // [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) //----------------------------------------------------------------------------- @@ -4235,11 +4280,12 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* ctx, const char* name) : DrawListInst(NUL SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX); LastFrameActive = -1; LastTimeActive = -1.0f; - FontWindowScale = 1.0f; + FontRefSize = 0.0f; + FontWindowScale = FontWindowScaleParents = 1.0f; SettingsOffset = -1; DrawList = &DrawListInst; - DrawList->_Data = &Ctx->DrawListSharedData; DrawList->_OwnerName = Name; + DrawList->_Data = &Ctx->DrawListSharedData; NavPreferredScoringPosRel[0] = NavPreferredScoringPosRel[1] = ImVec2(FLT_MAX, FLT_MAX); } @@ -4317,9 +4363,16 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) g.MovingWindow = NULL; } + // Store deactivate data + ImGuiDeactivatedItemData* deactivated_data = &g.DeactivatedItemData; + deactivated_data->ID = g.ActiveId; + deactivated_data->ElapseFrame = (g.LastItemData.ID == g.ActiveId) ? g.FrameCount : g.FrameCount + 1; // FIXME: OK to use LastItemData? + deactivated_data->HasBeenEditedBefore = g.ActiveIdHasBeenEditedBefore; + deactivated_data->IsAlive = (g.ActiveIdIsAlive == g.ActiveId); + // This could be written in a more general way (e.g associate a hook to ActiveId), // but since this is currently quite an exception we'll leave it as is. - // One common scenario leading to this is: pressing Key ->NavMoveRequestApplyResult() -> ClearActiveId() + // One common scenario leading to this is: pressing Key ->NavMoveRequestApplyResult() -> ClearActiveID() if (g.InputTextState.ID == g.ActiveId) InputTextDeactivateHook(g.ActiveId); } @@ -4643,15 +4696,27 @@ bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id) // This is also inlined in ItemAdd() // Note: if ImGuiItemStatusFlags_HasDisplayRect is set, user needs to set g.LastItemData.DisplayRect. -void ImGui::SetLastItemData(ImGuiID item_id, ImGuiItemFlags in_flags, ImGuiItemStatusFlags item_flags, const ImRect& item_rect) +void ImGui::SetLastItemData(ImGuiID item_id, ImGuiItemFlags item_flags, ImGuiItemStatusFlags status_flags, const ImRect& item_rect) { ImGuiContext& g = *GImGui; g.LastItemData.ID = item_id; - g.LastItemData.ItemFlags = in_flags; - g.LastItemData.StatusFlags = item_flags; + g.LastItemData.ItemFlags = item_flags; + g.LastItemData.StatusFlags = status_flags; g.LastItemData.Rect = g.LastItemData.NavRect = item_rect; } +static void ImGui::SetLastItemDataForWindow(ImGuiWindow* window, const ImRect& rect) +{ + ImGuiContext& g = *GImGui; + SetLastItemData(window->MoveId, g.CurrentItemFlags, window->DC.WindowItemStatusFlags, rect); +} + +static void ImGui::SetLastItemDataForChildWindowItem(ImGuiWindow* window, const ImRect& rect) +{ + ImGuiContext& g = *GImGui; + SetLastItemData(window->ChildId, g.CurrentItemFlags, window->DC.ChildItemStatusFlags, rect); +} + float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x) { if (wrap_pos_x < 0.0f) @@ -4712,15 +4777,15 @@ void ImGui::DebugAllocHook(ImGuiDebugAllocInfo* info, int frame_count, void* ptr } if (size != (size_t)-1) { + //printf("[%05d] MemAlloc(%d) -> 0x%p\n", frame_count, (int)size, ptr); entry->AllocCount++; info->TotalAllocCount++; - //printf("[%05d] MemAlloc(%d) -> 0x%p\n", frame_count, size, ptr); } else { + //printf("[%05d] MemFree(0x%p)\n", frame_count, ptr); entry->FreeCount++; info->TotalFreeCount++; - //printf("[%05d] MemFree(0x%p)\n", frame_count, ptr); } } @@ -4748,6 +4813,13 @@ ImGuiIO& ImGui::GetIO() return GImGui->IO; } +// This variant exists to facilitate backends experimenting with multi-threaded parallel context. (#8069, #6293, #5856) +ImGuiIO& ImGui::GetIOEx(ImGuiContext* ctx) +{ + IM_ASSERT(ctx != NULL); + return ctx->IO; +} + ImGuiPlatformIO& ImGui::GetPlatformIO() { IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext()?"); @@ -5030,7 +5102,8 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() io.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false; } -// Calling SetupDrawListSharedData() is followed by SetCurrentFont() which sets up the remaining data. +// Called once a frame. Followed by SetCurrentFont() which sets up the remaining data. +// FIXME-VIEWPORT: the concept of a single ClipRectFullscreen is not ideal! static void SetupDrawListSharedData() { ImGuiContext& g = *GImGui; @@ -5140,11 +5213,8 @@ void ImGui::NewFrame() g.ActiveIdTimer += g.IO.DeltaTime; g.LastActiveIdTimer += g.IO.DeltaTime; g.ActiveIdPreviousFrame = g.ActiveId; - g.ActiveIdPreviousFrameWindow = g.ActiveIdWindow; - g.ActiveIdPreviousFrameHasBeenEditedBefore = g.ActiveIdHasBeenEditedBefore; g.ActiveIdIsAlive = 0; g.ActiveIdHasBeenEditedThisFrame = false; - g.ActiveIdPreviousFrameIsAlive = false; g.ActiveIdIsJustActivated = false; if (g.TempInputId != 0 && g.ActiveId != g.TempInputId) g.TempInputId = 0; @@ -5153,6 +5223,9 @@ void ImGui::NewFrame() g.ActiveIdUsingNavDirMask = 0x00; g.ActiveIdUsingAllKeyboardKeys = false; } + if (g.DeactivatedItemData.ElapseFrame < g.FrameCount) + g.DeactivatedItemData.ID = 0; + g.DeactivatedItemData.IsAlive = false; // Record when we have been stationary as this state is preserved while over same item. // FIXME: The way this is expressed means user cannot alter HoverStationaryDelay during the frame to use varying values. @@ -5784,16 +5857,16 @@ bool ImGui::IsItemDeactivated() ImGuiContext& g = *GImGui; if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDeactivated) return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Deactivated) != 0; - return (g.ActiveIdPreviousFrame == g.LastItemData.ID && g.ActiveIdPreviousFrame != 0 && g.ActiveId != g.LastItemData.ID); + return (g.DeactivatedItemData.ID == g.LastItemData.ID && g.LastItemData.ID != 0 && g.DeactivatedItemData.ElapseFrame >= g.FrameCount); } bool ImGui::IsItemDeactivatedAfterEdit() { ImGuiContext& g = *GImGui; - return IsItemDeactivated() && (g.ActiveIdPreviousFrameHasBeenEditedBefore || (g.ActiveId == 0 && g.ActiveIdHasBeenEditedBefore)); + return IsItemDeactivated() && g.DeactivatedItemData.HasBeenEditedBefore; } -// == GetItemID() == GetFocusID() +// == (GetItemID() == GetFocusID() && GetFocusID() != 0) bool ImGui::IsItemFocused() { ImGuiContext& g = *GImGui; @@ -6000,9 +6073,12 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I } SetNextWindowSize(size); - // Forward child flags + // Forward child flags (we allow prior settings to merge but it'll only work for adding flags) + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasChildFlags) + g.NextWindowData.ChildFlags |= child_flags; + else + g.NextWindowData.ChildFlags = child_flags; g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasChildFlags; - g.NextWindowData.ChildFlags = child_flags; // Build up name. If you need to append to a same child from multiple location in the ID stack, use BeginChild(ImGuiID id) with a stable value. // FIXME: 2023/11/14: commented out shorted version. We had an issue with multiple ### in child window path names, which the trailing hash helped workaround. @@ -6060,10 +6136,10 @@ void ImGui::EndChild() ImGuiContext& g = *GImGui; ImGuiWindow* child_window = g.CurrentWindow; - IM_ASSERT(g.WithinEndChild == false); + const ImGuiID backup_within_end_child_id = g.WithinEndChildID; IM_ASSERT(child_window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() calls - g.WithinEndChild = true; + g.WithinEndChildID = child_window->ID; ImVec2 child_size = child_window->Size; End(); if (child_window->BeginCount == 1) @@ -6094,8 +6170,15 @@ void ImGui::EndChild() } if (g.HoveredWindow == child_window) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; + child_window->DC.ChildItemStatusFlags = g.LastItemData.StatusFlags; + //SetLastItemDataForChildWindowItem(child_window, child_window->Rect()); // Not needed, effectively done by ItemAdd() } - g.WithinEndChild = false; + else + { + SetLastItemDataForChildWindowItem(child_window, child_window->Rect()); + } + + g.WithinEndChildID = backup_within_end_child_id; g.LogLinePosY = -FLT_MAX; // To enforce a carriage return } @@ -6126,29 +6209,6 @@ static void ApplyWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settin window->Collapsed = settings->Collapsed; } -static void UpdateWindowInFocusOrderList(ImGuiWindow* window, bool just_created, ImGuiWindowFlags new_flags) -{ - ImGuiContext& g = *GImGui; - - const bool new_is_explicit_child = (new_flags & ImGuiWindowFlags_ChildWindow) != 0 && ((new_flags & ImGuiWindowFlags_Popup) == 0 || (new_flags & ImGuiWindowFlags_ChildMenu) != 0); - const bool child_flag_changed = new_is_explicit_child != window->IsExplicitChild; - if ((just_created || child_flag_changed) && !new_is_explicit_child) - { - IM_ASSERT(!g.WindowsFocusOrder.contains(window)); - g.WindowsFocusOrder.push_back(window); - window->FocusOrder = (short)(g.WindowsFocusOrder.Size - 1); - } - else if (!just_created && child_flag_changed && new_is_explicit_child) - { - IM_ASSERT(g.WindowsFocusOrder[window->FocusOrder] == window); - for (int n = window->FocusOrder + 1; n < g.WindowsFocusOrder.Size; n++) - g.WindowsFocusOrder[n]->FocusOrder--; - g.WindowsFocusOrder.erase(g.WindowsFocusOrder.Data + window->FocusOrder); - window->FocusOrder = -1; - } - window->IsExplicitChild = new_is_explicit_child; -} - static void InitOrLoadWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settings) { // Initial window state with e.g. default/arbitrary window position @@ -6672,7 +6732,7 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar)) { float y = window->Pos.y + window->TitleBarHeight - 1; - window->DrawList->AddLine(ImVec2(window->Pos.x + border_size, y), ImVec2(window->Pos.x + window->Size.x - border_size, y), border_col, g.Style.FrameBorderSize); + window->DrawList->AddLine(ImVec2(window->Pos.x + border_size * 0.5f, y), ImVec2(window->Pos.x + window->Size.x - border_size * 0.5f, y), border_col, g.Style.FrameBorderSize); } } @@ -6684,9 +6744,10 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar ImGuiStyle& style = g.Style; ImGuiWindowFlags flags = window->Flags; - // Ensure that ScrollBar doesn't read last frame's SkipItems + // Ensure that Scrollbar() doesn't read last frame's SkipItems IM_ASSERT(window->BeginCount == 0); window->SkipItems = false; + window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; // Draw window + handle manual resize // As we highlight the title bar when want_focus is set, multiple reappearing windows will have their title bar highlighted on their reappearing frame. @@ -6731,9 +6792,9 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar { ImRect menu_bar_rect = window->MenuBarRect(); menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. - window->DrawList->AddRectFilled(menu_bar_rect.Min + ImVec2(window_border_size, 0), menu_bar_rect.Max - ImVec2(window_border_size, 0), GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawFlags_RoundCornersTop); + window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawFlags_RoundCornersTop); if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) - window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + window->DrawList->AddLine(menu_bar_rect.GetBL() + ImVec2(window_border_size * 0.5f, 0.0f), menu_bar_rect.GetBR() - ImVec2(window_border_size * 0.5f, 0.0f), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); } // Scrollbars @@ -6752,9 +6813,10 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar continue; const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPosN); - window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(window_border_size, resize_grip_draw_size) : ImVec2(resize_grip_draw_size, window_border_size))); - window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(resize_grip_draw_size, window_border_size) : ImVec2(window_border_size, resize_grip_draw_size))); - window->DrawList->PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + window_border_size), corner.y + grip.InnerDir.y * (window_rounding + window_border_size)), window_rounding, grip.AngleMin12, grip.AngleMax12); + const float border_inner = IM_ROUND(window_border_size * 0.5f); + window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(border_inner, resize_grip_draw_size) : ImVec2(resize_grip_draw_size, border_inner))); + window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(resize_grip_draw_size, border_inner) : ImVec2(border_inner, resize_grip_draw_size))); + window->DrawList->PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + border_inner), corner.y + grip.InnerDir.y * (window_rounding + border_inner)), window_rounding, grip.AngleMin12, grip.AngleMax12); window->DrawList->PathFillConvex(col); } } @@ -6763,6 +6825,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar if (handle_borders_and_resize_grips) RenderWindowOuterBorders(window); } + window->DC.NavLayerCurrent = ImGuiNavLayer_Main; } // Render title text, collapse button, close button @@ -6908,42 +6971,6 @@ static void SetWindowActiveForSkipRefresh(ImGuiWindow* window) } } -// When a modal popup is open, newly created windows that want focus (i.e. are not popups and do not specify ImGuiWindowFlags_NoFocusOnAppearing) -// should be positioned behind that modal window, unless the window was created inside the modal begin-stack. -// In case of multiple stacked modals newly created window honors begin stack order and does not go below its own modal parent. -// - WindowA // FindBlockingModal() returns Modal1 -// - WindowB // .. returns Modal1 -// - Modal1 // .. returns Modal2 -// - WindowC // .. returns Modal2 -// - WindowD // .. returns Modal2 -// - Modal2 // .. returns Modal2 -// - WindowE // .. returns NULL -// Notes: -// - FindBlockingModal(NULL) == NULL is generally equivalent to GetTopMostPopupModal() == NULL. -// Only difference is here we check for ->Active/WasActive but it may be unnecessary. -ImGuiWindow* ImGui::FindBlockingModal(ImGuiWindow* window) -{ - ImGuiContext& g = *GImGui; - if (g.OpenPopupStack.Size <= 0) - return NULL; - - // Find a modal that has common parent with specified window. Specified window should be positioned behind that modal. - for (ImGuiPopupData& popup_data : g.OpenPopupStack) - { - ImGuiWindow* popup_window = popup_data.Window; - if (popup_window == NULL || !(popup_window->Flags & ImGuiWindowFlags_Modal)) - continue; - if (!popup_window->Active && !popup_window->WasActive) // Check WasActive, because this code may run before popup renders on current frame, also check Active to handle newly created windows. - continue; - if (window == NULL) // FindBlockingModal(NULL) test for if FocusWindow(NULL) is naturally possible via a mouse click. - return popup_window; - if (IsWindowWithinBeginStackOf(window, popup_window)) // Window may be over modal - continue; - return popup_window; // Place window right below first block modal - } - return NULL; -} - // Push a new Dear ImGui window to add widgets to. // - A default window called "Debug" is automatically stacked at the beginning of every frame so you can use widgets without explicitly calling a Begin/End pair. // - Begin/End can be called multiple times during the frame with the same window name to append content. @@ -7035,6 +7062,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // There's little point to expose a flag to set this: because the interesting cases won't be using parent_window_in_stack, // e.g. linking a tool window in a standalone viewport to a document window, regardless of their Begin() stack parenting. (#6798) window->ParentWindowForFocusRoute = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window_in_stack : NULL; + + // Inherent SetWindowFontScale() from parent until we fix this system... + window->FontWindowScaleParents = parent_window ? parent_window->FontWindowScaleParents * parent_window->FontWindowScale : 1.0f; } // Add to focus scope stack @@ -7196,6 +7226,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y; window->TitleBarHeight = (flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : g.FontSize + g.Style.FramePadding.y * 2.0f; window->MenuBarHeight = (flags & ImGuiWindowFlags_MenuBar) ? window->DC.MenuBarOffset.y + g.FontSize + g.Style.FramePadding.y * 2.0f : 0.0f; + window->FontRefSize = g.FontSize; // Lock this to discourage calling window->CalcFontSize() outside of current window. // Depending on condition we use previous or current window size to compare against contents size to decide if a scrollbar should be visible. // Those flags will be altered further down in the function depending on more conditions. @@ -7347,9 +7378,12 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { IM_ASSERT(window->IDStack.Size == 1); window->IDStack.Size = 0; // As window->IDStack[0] == window->ID here, make sure TestEngine doesn't erroneously see window as parent of itself. + window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; IMGUI_TEST_ENGINE_ITEM_ADD(window->ID, window->Rect(), NULL); IMGUI_TEST_ENGINE_ITEM_INFO(window->ID, window->Name, (g.HoveredWindow == window) ? ImGuiItemStatusFlags_HoveredRect : 0); window->IDStack.Size = 1; + window->DC.NavLayerCurrent = ImGuiNavLayer_Main; + } #endif @@ -7579,6 +7613,13 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (want_focus && window == g.NavWindow) NavInitWindow(window, false); // <-- this is in the way for us to be able to defer and sort reappearing FocusWindow() calls + // Pressing CTRL+C copy window content into the clipboard + // [EXPERIMENTAL] Breaks on nested Begin/End pairs. We need to work that out and add better logging scope. + // [EXPERIMENTAL] Text outputs has many issues. + if (g.IO.ConfigWindowsCopyContentsWithCtrlC) + if (g.NavWindow && g.NavWindow->RootWindow == window && g.ActiveId == 0 && Shortcut(ImGuiMod_Ctrl | ImGuiKey_C)) + LogToClipboard(0); + // Title bar if (!(flags & ImGuiWindowFlags_NoTitleBar)) RenderWindowTitleBarContents(window, ImRect(title_bar_rect.Min.x + window->WindowBorderSize, title_bar_rect.Min.y, title_bar_rect.Max.x - window->WindowBorderSize, title_bar_rect.Max.y), name, p_open); @@ -7589,18 +7630,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (flags & ImGuiWindowFlags_Tooltip) g.TooltipPreviousWindow = window; - // Pressing CTRL+C while holding on a window copy its content to the clipboard - // This works but 1. doesn't handle multiple Begin/End pairs, 2. recursing into another Begin/End pair - so we need to work that out and add better logging scope. - // Maybe we can support CTRL+C on every element? - /* - //if (g.NavWindow == window && g.ActiveId == 0) - if (g.ActiveId == window->MoveId) - if (g.IO.KeyCtrl && IsKeyPressed(ImGuiKey_C)) - LogToClipboard(); - */ - // We fill last item data based on Title Bar/Tab, in order for IsItemHovered() and IsItemActive() to be usable after Begin(). // This is useful to allow creating context menus on title bar only, etc. + window->DC.WindowItemStatusFlags = ImGuiItemStatusFlags_None; + window->DC.WindowItemStatusFlags |= IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0; SetLastItemDataForWindow(window, title_bar_rect); // [DEBUG] @@ -7612,7 +7645,11 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // [Test Engine] Register title bar / tab with MoveId. #ifdef IMGUI_ENABLE_TEST_ENGINE if (!(window->Flags & ImGuiWindowFlags_NoTitleBar)) + { + window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; IMGUI_TEST_ENGINE_ITEM_ADD(g.LastItemData.ID, g.LastItemData.Rect, &g.LastItemData); + window->DC.NavLayerCurrent = ImGuiNavLayer_Main; + } #endif } else @@ -7702,12 +7739,6 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) return !window->SkipItems; } -static void ImGui::SetLastItemDataForWindow(ImGuiWindow* window, const ImRect& rect) -{ - ImGuiContext& g = *GImGui; - SetLastItemData(window->MoveId, g.CurrentItemFlags, IsMouseHoveringRect(rect.Min, rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, rect); -} - void ImGui::End() { ImGuiContext& g = *GImGui; @@ -7723,7 +7754,7 @@ void ImGui::End() // Error checking: verify that user doesn't directly call End() on a child window. if (window->Flags & ImGuiWindowFlags_ChildWindow) - IM_ASSERT_USER_ERROR(g.WithinEndChild, "Must call EndChild() and not End()!"); + IM_ASSERT_USER_ERROR(g.WithinEndChildID == window->ID, "Must call EndChild() and not End()!"); // Close anything that is open if (window->DC.CurrentColumns) @@ -7741,7 +7772,7 @@ void ImGui::End() } // Stop logging - if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging + if (g.LogWindow == window) // FIXME: add more options for scope of logging LogFinish(); if (window->DC.IsSetPos) @@ -7762,176 +7793,6 @@ void ImGui::End() SetCurrentWindow(g.CurrentWindowStack.Size == 0 ? NULL : g.CurrentWindowStack.back().Window); } -void ImGui::BringWindowToFocusFront(ImGuiWindow* window) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(window == window->RootWindow); - - const int cur_order = window->FocusOrder; - IM_ASSERT(g.WindowsFocusOrder[cur_order] == window); - if (g.WindowsFocusOrder.back() == window) - return; - - const int new_order = g.WindowsFocusOrder.Size - 1; - for (int n = cur_order; n < new_order; n++) - { - g.WindowsFocusOrder[n] = g.WindowsFocusOrder[n + 1]; - g.WindowsFocusOrder[n]->FocusOrder--; - IM_ASSERT(g.WindowsFocusOrder[n]->FocusOrder == n); - } - g.WindowsFocusOrder[new_order] = window; - window->FocusOrder = (short)new_order; -} - -void ImGui::BringWindowToDisplayFront(ImGuiWindow* window) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* current_front_window = g.Windows.back(); - if (current_front_window == window || current_front_window->RootWindow == window) // Cheap early out (could be better) - return; - for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the top-most window - if (g.Windows[i] == window) - { - memmove(&g.Windows[i], &g.Windows[i + 1], (size_t)(g.Windows.Size - i - 1) * sizeof(ImGuiWindow*)); - g.Windows[g.Windows.Size - 1] = window; - break; - } -} - -void ImGui::BringWindowToDisplayBack(ImGuiWindow* window) -{ - ImGuiContext& g = *GImGui; - if (g.Windows[0] == window) - return; - for (int i = 0; i < g.Windows.Size; i++) - if (g.Windows[i] == window) - { - memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow*)); - g.Windows[0] = window; - break; - } -} - -void ImGui::BringWindowToDisplayBehind(ImGuiWindow* window, ImGuiWindow* behind_window) -{ - IM_ASSERT(window != NULL && behind_window != NULL); - ImGuiContext& g = *GImGui; - window = window->RootWindow; - behind_window = behind_window->RootWindow; - int pos_wnd = FindWindowDisplayIndex(window); - int pos_beh = FindWindowDisplayIndex(behind_window); - if (pos_wnd < pos_beh) - { - size_t copy_bytes = (pos_beh - pos_wnd - 1) * sizeof(ImGuiWindow*); - memmove(&g.Windows.Data[pos_wnd], &g.Windows.Data[pos_wnd + 1], copy_bytes); - g.Windows[pos_beh - 1] = window; - } - else - { - size_t copy_bytes = (pos_wnd - pos_beh) * sizeof(ImGuiWindow*); - memmove(&g.Windows.Data[pos_beh + 1], &g.Windows.Data[pos_beh], copy_bytes); - g.Windows[pos_beh] = window; - } -} - -int ImGui::FindWindowDisplayIndex(ImGuiWindow* window) -{ - ImGuiContext& g = *GImGui; - return g.Windows.index_from_ptr(g.Windows.find(window)); -} - -// Moving window to front of display and set focus (which happens to be back of our sorted list) -void ImGui::FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags) -{ - ImGuiContext& g = *GImGui; - - // Modal check? - if ((flags & ImGuiFocusRequestFlags_UnlessBelowModal) && (g.NavWindow != window)) // Early out in common case. - if (ImGuiWindow* blocking_modal = FindBlockingModal(window)) - { - // This block would typically be reached in two situations: - // - API call to FocusWindow() with a window under a modal and ImGuiFocusRequestFlags_UnlessBelowModal flag. - // - User clicking on void or anything behind a modal while a modal is open (window == NULL) - IMGUI_DEBUG_LOG_FOCUS("[focus] FocusWindow(\"%s\", UnlessBelowModal): prevented by \"%s\".\n", window ? window->Name : "", blocking_modal->Name); - if (window && window == window->RootWindow && (window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0) - BringWindowToDisplayBehind(window, blocking_modal); // Still bring right under modal. (FIXME: Could move in focus list too?) - ClosePopupsOverWindow(GetTopMostPopupModal(), false); // Note how we need to use GetTopMostPopupModal() aad NOT blocking_modal, to handle nested modals - return; - } - - // Find last focused child (if any) and focus it instead. - if ((flags & ImGuiFocusRequestFlags_RestoreFocusedChild) && window != NULL) - window = NavRestoreLastChildNavWindow(window); - - // Apply focus - if (g.NavWindow != window) - { - SetNavWindow(window); - if (window && g.NavHighlightItemUnderNav) - g.NavMousePosDirty = true; - g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId - g.NavLayer = ImGuiNavLayer_Main; - SetNavFocusScope(window ? window->NavRootFocusScopeId : 0); - g.NavIdIsAlive = false; - g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid; - - // Close popups if any - ClosePopupsOverWindow(window, false); - } - - // Move the root window to the top of the pile - IM_ASSERT(window == NULL || window->RootWindow != NULL); - ImGuiWindow* focus_front_window = window ? window->RootWindow : NULL; // NB: In docking branch this is window->RootWindowDockStop - ImGuiWindow* display_front_window = window ? window->RootWindow : NULL; - - // Steal active widgets. Some of the cases it triggers includes: - // - Focus a window while an InputText in another window is active, if focus happens before the old InputText can run. - // - When using Nav to activate menu items (due to timing of activating on press->new window appears->losing ActiveId) - if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != focus_front_window) - if (!g.ActiveIdNoClearOnFocusLoss) - ClearActiveID(); - - // Passing NULL allow to disable keyboard focus - if (!window) - return; - - // Bring to front - BringWindowToFocusFront(focus_front_window); - if (((window->Flags | display_front_window->Flags) & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0) - BringWindowToDisplayFront(display_front_window); -} - -void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWindow* ignore_window, ImGuiViewport* filter_viewport, ImGuiFocusRequestFlags flags) -{ - ImGuiContext& g = *GImGui; - IM_UNUSED(filter_viewport); // Unused in master branch. - int start_idx = g.WindowsFocusOrder.Size - 1; - if (under_this_window != NULL) - { - // Aim at root window behind us, if we are in a child window that's our own root (see #4640) - int offset = -1; - while (under_this_window->Flags & ImGuiWindowFlags_ChildWindow) - { - under_this_window = under_this_window->ParentWindow; - offset = 0; - } - start_idx = FindWindowFocusIndex(under_this_window) + offset; - } - for (int i = start_idx; i >= 0; i--) - { - // We may later decide to test for different NoXXXInputs based on the active navigation input (mouse vs nav) but that may feel more confusing to the user. - ImGuiWindow* window = g.WindowsFocusOrder[i]; - if (window == ignore_window || !window->WasActive) - continue; - if ((window->Flags & (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) != (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) - { - FocusWindow(window, flags); - return; - } - } - FocusWindow(NULL, flags); -} - // Important: this alone doesn't alter current ImDrawList state. This is called by PushFont/PopFont only. void ImGui::SetCurrentFont(ImFont* font) { @@ -8201,36 +8062,6 @@ bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) return true; } -bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* ref_window = g.NavWindow; - ImGuiWindow* cur_window = g.CurrentWindow; - - if (ref_window == NULL) - return false; - if (flags & ImGuiFocusedFlags_AnyWindow) - return true; - - IM_ASSERT(cur_window); // Not inside a Begin()/End() - const bool popup_hierarchy = (flags & ImGuiFocusedFlags_NoPopupHierarchy) == 0; - if (flags & ImGuiHoveredFlags_RootWindow) - cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy); - - if (flags & ImGuiHoveredFlags_ChildWindows) - return IsWindowChildOf(ref_window, cur_window, popup_hierarchy); - else - return (ref_window == cur_window); -} - -// Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext) -// Note that NoNavFocus makes the window not reachable with CTRL+TAB but it can still be focused with mouse or programmatically. -// If you want a window to never be focused, you may use the e.g. NoInputs flag. -bool ImGui::IsWindowNavFocusable(ImGuiWindow* window) -{ - return window->WasActive && window == window->RootWindow && !(window->Flags & ImGuiWindowFlags_NoNavFocus); -} - float ImGui::GetWindowWidth() { ImGuiWindow* window = GImGui->CurrentWindow; @@ -8378,24 +8209,6 @@ void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond) SetWindowCollapsed(window, collapsed, cond); } -void ImGui::SetWindowFocus() -{ - FocusWindow(GImGui->CurrentWindow); -} - -void ImGui::SetWindowFocus(const char* name) -{ - if (name) - { - if (ImGuiWindow* window = FindWindowByName(name)) - FocusWindow(window); - } - else - { - FocusWindow(NULL); - } -} - void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot) { ImGuiContext& g = *GImGui; @@ -8453,12 +8266,6 @@ void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond) g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always; } -void ImGui::SetNextWindowFocus() -{ - ImGuiContext& g = *GImGui; - g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasFocus; -} - void ImGui::SetNextWindowBgAlpha(float alpha) { ImGuiContext& g = *GImGui; @@ -8920,27 +8727,10 @@ ImGuiKeyData* ImGui::GetKeyData(ImGuiContext* ctx, ImGuiKey key) if (key & ImGuiMod_Mask_) key = ConvertSingleModFlagToKey(key); -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - IM_ASSERT(key >= ImGuiKey_LegacyNativeKey_BEGIN && key < ImGuiKey_NamedKey_END); - if (IsLegacyKey(key) && g.IO.KeyMap[key] != -1) - key = (ImGuiKey)g.IO.KeyMap[key]; // Remap native->imgui or imgui->native -#else IM_ASSERT(IsNamedKey(key) && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend & user code."); -#endif - return &g.IO.KeysData[key - ImGuiKey_KeysData_OFFSET]; + return &g.IO.KeysData[key - ImGuiKey_NamedKey_BEGIN]; } -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO -// Formally moved to obsolete section in 1.90.5 in spite of documented as obsolete since 1.87 -ImGuiKey ImGui::GetKeyIndex(ImGuiKey key) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(IsNamedKey(key)); - const ImGuiKeyData* key_data = GetKeyData(key); - return (ImGuiKey)(key_data - g.IO.KeysData); -} -#endif - // Those names a provided for debugging purpose and are not meant to be saved persistently not compared. static const char* const GKeyNames[] = { @@ -8972,18 +8762,7 @@ const char* ImGui::GetKeyName(ImGuiKey key) { if (key == ImGuiKey_None) return "None"; -#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO IM_ASSERT(IsNamedKeyOrMod(key) && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend and user code."); -#else - ImGuiContext& g = *GImGui; - if (IsLegacyKey(key)) - { - if (g.IO.KeyMap[key] == -1) - return "N/A"; - IM_ASSERT(IsNamedKey((ImGuiKey)g.IO.KeyMap[key])); - key = (ImGuiKey)g.IO.KeyMap[key]; - } -#endif if (key & ImGuiMod_Mask_) key = ConvertSingleModFlagToKey(key); if (!IsNamedKey(key)) @@ -9451,6 +9230,17 @@ bool ImGui::IsMouseReleased(ImGuiMouseButton button, ImGuiID owner_id) return g.IO.MouseReleased[button] && TestKeyOwner(MouseButtonToKey(button), owner_id); // Should be same as IsKeyReleased(MouseButtonToKey(button), owner_id) } +// Use if you absolutely need to distinguish single-click from double-click by introducing a delay. +// Generally use with 'delay >= io.MouseDoubleClickTime' + combined with a 'io.MouseClickedLastCount == 1' test. +// This is a very rarely used UI idiom, but some apps use this: e.g. MS Explorer single click on an icon to rename. +bool ImGui::IsMouseReleasedWithDelay(ImGuiMouseButton button, float delay) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + const float time_since_release = (float)(g.Time - g.IO.MouseReleasedTime[button]); + return !IsMouseDown(button) && (time_since_release - g.IO.DeltaTime < delay) && (time_since_release >= delay); +} + bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; @@ -9627,74 +9417,6 @@ static void ImGui::UpdateKeyboardInputs() if (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard) io.ClearInputKeys(); - // Import legacy keys or verify they are not used -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - if (io.BackendUsingLegacyKeyArrays == 0) - { - // Backend used new io.AddKeyEvent() API: Good! Verify that old arrays are never written to externally. - for (int n = 0; n < ImGuiKey_LegacyNativeKey_END; n++) - IM_ASSERT((io.KeysDown[n] == false || IsKeyDown((ImGuiKey)n)) && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!"); - } - else - { - if (g.FrameCount == 0) - for (int n = ImGuiKey_LegacyNativeKey_BEGIN; n < ImGuiKey_LegacyNativeKey_END; n++) - IM_ASSERT(g.IO.KeyMap[n] == -1 && "Backend is not allowed to write to io.KeyMap[0..511]!"); - - // Build reverse KeyMap (Named -> Legacy) - for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_NamedKey_END; n++) - if (io.KeyMap[n] != -1) - { - IM_ASSERT(IsLegacyKey((ImGuiKey)io.KeyMap[n])); - io.KeyMap[io.KeyMap[n]] = n; - } - - // Import legacy keys into new ones - for (int n = ImGuiKey_LegacyNativeKey_BEGIN; n < ImGuiKey_LegacyNativeKey_END; n++) - if (io.KeysDown[n] || io.BackendUsingLegacyKeyArrays == 1) - { - const ImGuiKey key = (ImGuiKey)(io.KeyMap[n] != -1 ? io.KeyMap[n] : n); - IM_ASSERT(io.KeyMap[n] == -1 || IsNamedKey(key)); - io.KeysData[key].Down = io.KeysDown[n]; - if (key != n) - io.KeysDown[key] = io.KeysDown[n]; // Allow legacy code using io.KeysDown[GetKeyIndex()] with old backends - io.BackendUsingLegacyKeyArrays = 1; - } - if (io.BackendUsingLegacyKeyArrays == 1) - { - GetKeyData(ImGuiMod_Ctrl)->Down = io.KeyCtrl; - GetKeyData(ImGuiMod_Shift)->Down = io.KeyShift; - GetKeyData(ImGuiMod_Alt)->Down = io.KeyAlt; - GetKeyData(ImGuiMod_Super)->Down = io.KeySuper; - } - } -#endif - - // Import legacy ImGuiNavInput_ io inputs and convert to gamepad keys -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; - if (io.BackendUsingLegacyNavInputArray && nav_gamepad_active) - { - #define MAP_LEGACY_NAV_INPUT_TO_KEY1(_KEY, _NAV1) do { io.KeysData[_KEY].Down = (io.NavInputs[_NAV1] > 0.0f); io.KeysData[_KEY].AnalogValue = io.NavInputs[_NAV1]; } while (0) - #define MAP_LEGACY_NAV_INPUT_TO_KEY2(_KEY, _NAV1, _NAV2) do { io.KeysData[_KEY].Down = (io.NavInputs[_NAV1] > 0.0f) || (io.NavInputs[_NAV2] > 0.0f); io.KeysData[_KEY].AnalogValue = ImMax(io.NavInputs[_NAV1], io.NavInputs[_NAV2]); } while (0) - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceDown, ImGuiNavInput_Activate); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceRight, ImGuiNavInput_Cancel); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceLeft, ImGuiNavInput_Menu); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceUp, ImGuiNavInput_Input); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadLeft, ImGuiNavInput_DpadLeft); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadRight, ImGuiNavInput_DpadRight); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadUp, ImGuiNavInput_DpadUp); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadDown, ImGuiNavInput_DpadDown); - MAP_LEGACY_NAV_INPUT_TO_KEY2(ImGuiKey_GamepadL1, ImGuiNavInput_FocusPrev, ImGuiNavInput_TweakSlow); - MAP_LEGACY_NAV_INPUT_TO_KEY2(ImGuiKey_GamepadR1, ImGuiNavInput_FocusNext, ImGuiNavInput_TweakFast); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickLeft, ImGuiNavInput_LStickLeft); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickRight, ImGuiNavInput_LStickRight); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickUp, ImGuiNavInput_LStickUp); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickDown, ImGuiNavInput_LStickDown); - #undef NAV_MAP_KEY - } -#endif - // Update aliases for (int n = 0; n < ImGuiMouseButton_COUNT; n++) UpdateAliasKey(MouseButtonToKey(n), io.MouseDown[n], io.MouseDown[n] ? 1.0f : 0.0f); @@ -9718,22 +9440,21 @@ static void ImGui::UpdateKeyboardInputs() // Clear gamepad data if disabled if ((io.BackendFlags & ImGuiBackendFlags_HasGamepad) == 0) - for (int i = ImGuiKey_Gamepad_BEGIN; i < ImGuiKey_Gamepad_END; i++) + for (int key = ImGuiKey_Gamepad_BEGIN; key < ImGuiKey_Gamepad_END; key++) { - io.KeysData[i - ImGuiKey_KeysData_OFFSET].Down = false; - io.KeysData[i - ImGuiKey_KeysData_OFFSET].AnalogValue = 0.0f; + io.KeysData[key - ImGuiKey_NamedKey_BEGIN].Down = false; + io.KeysData[key - ImGuiKey_NamedKey_BEGIN].AnalogValue = 0.0f; } // Update keys - for (int i = 0; i < ImGuiKey_KeysData_SIZE; i++) + for (int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key++) { - ImGuiKeyData* key_data = &io.KeysData[i]; + ImGuiKeyData* key_data = &io.KeysData[key - ImGuiKey_NamedKey_BEGIN]; key_data->DownDurationPrev = key_data->DownDuration; key_data->DownDuration = key_data->Down ? (key_data->DownDuration < 0.0f ? 0.0f : key_data->DownDuration + io.DeltaTime) : -1.0f; if (key_data->DownDuration == 0.0f) { - ImGuiKey key = (ImGuiKey)(ImGuiKey_KeysData_OFFSET + i); - if (IsKeyboardKey(key)) + if (IsKeyboardKey((ImGuiKey)key)) g.LastKeyboardKeyPressTime = g.Time; else if (key == ImGuiKey_ReservedForModCtrl || key == ImGuiKey_ReservedForModShift || key == ImGuiKey_ReservedForModAlt || key == ImGuiKey_ReservedForModSuper) g.LastKeyboardKeyPressTime = g.Time; @@ -9743,7 +9464,7 @@ static void ImGui::UpdateKeyboardInputs() // Update keys/input owner (named keys only): one entry per key for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { - ImGuiKeyData* key_data = &io.KeysData[key - ImGuiKey_KeysData_OFFSET]; + ImGuiKeyData* key_data = &io.KeysData[key - ImGuiKey_NamedKey_BEGIN]; ImGuiKeyOwnerData* owner_data = &g.KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN]; owner_data->OwnerCurr = owner_data->OwnerNext; if (!key_data->Down) // Important: ownership is released on the frame after a release. Ensure a 'MouseDown -> CloseWindow -> MouseUp' chain doesn't lead to someone else seeing the MouseUp. @@ -9793,6 +9514,8 @@ static void ImGui::UpdateMouseInputs() io.MouseClicked[i] = io.MouseDown[i] && io.MouseDownDuration[i] < 0.0f; io.MouseClickedCount[i] = 0; // Will be filled below io.MouseReleased[i] = !io.MouseDown[i] && io.MouseDownDuration[i] >= 0.0f; + if (io.MouseReleased[i]) + io.MouseReleasedTime[i] = g.Time; io.MouseDownDurationPrev[i] = io.MouseDownDuration[i]; io.MouseDownDuration[i] = io.MouseDown[i] ? (io.MouseDownDuration[i] < 0.0f ? 0.0f : io.MouseDownDuration[i] + io.DeltaTime) : -1.0f; if (io.MouseClicked[i]) @@ -9961,7 +9684,7 @@ void ImGui::UpdateMouseWheel() { LockWheelingWindow(window, wheel.x); float max_step = window->InnerRect.GetWidth() * 0.67f; - float scroll_step = ImTrunc(ImMin(2 * window->CalcFontSize(), max_step)); + float scroll_step = ImTrunc(ImMin(2 * window->FontRefSize, max_step)); SetScrollX(window, window->Scroll.x - wheel.x * scroll_step); g.WheelingWindowScrolledFrame = g.FrameCount; } @@ -9969,7 +9692,7 @@ void ImGui::UpdateMouseWheel() { LockWheelingWindow(window, wheel.y); float max_step = window->InnerRect.GetHeight() * 0.67f; - float scroll_step = ImTrunc(ImMin(5 * window->CalcFontSize(), max_step)); + float scroll_step = ImTrunc(ImMin(5 * window->FontRefSize, max_step)); SetScrollY(window, window->Scroll.y - wheel.y * scroll_step); g.WheelingWindowScrolledFrame = g.FrameCount; } @@ -10029,7 +9752,7 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) bool mouse_moved = false, mouse_wheeled = false, key_changed = false, key_changed_nonchar = false, text_inputted = false; int mouse_button_changed = 0x00; - ImBitArray key_changed_mask; + ImBitArray key_changed_mask; int event_n = 0; for (; event_n < g.InputEventsQueue.Size; event_n++) @@ -10092,13 +9815,6 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) key_changed_mask.SetBit(key_data_index); if (trickle_interleaved_nonchar_keys_and_text && !key_is_potentially_for_char_input) key_changed_nonchar = true; - - // Allow legacy code using io.KeysDown[GetKeyIndex()] with new backends -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - io.KeysDown[key_data_index] = key_data->Down; - if (io.KeyMap[key_data_index] != -1) - io.KeysDown[io.KeyMap[key_data_index]] = key_data->Down; -#endif } else if (e->Type == ImGuiInputEventType_Text) { @@ -10359,7 +10075,6 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID own return true; } - //----------------------------------------------------------------------------- // [SECTION] ERROR CHECKING, STATE RECOVERY //----------------------------------------------------------------------------- @@ -10460,14 +10175,6 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() IM_ASSERT(g.Style.WindowMinSize.x >= 1.0f && g.Style.WindowMinSize.y >= 1.0f && "Invalid style setting."); IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left || g.Style.WindowMenuButtonPosition == ImGuiDir_Right); IM_ASSERT(g.Style.ColorButtonPosition == ImGuiDir_Left || g.Style.ColorButtonPosition == ImGuiDir_Right); -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_COUNT; n++) - IM_ASSERT(g.IO.KeyMap[n] >= -1 && g.IO.KeyMap[n] < ImGuiKey_LegacyNativeKey_END && "io.KeyMap[] contains an out of bound value (need to be 0..511, or -1 for unmapped key)"); - - // Check: required key mapping (we intentionally do NOT check all keys to not pressure user into setting up everything, but Space is required and was only added in 1.60 WIP) - if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && g.IO.BackendUsingLegacyKeyArrays == 1) - IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation."); -#endif // Error handling: we do not accept 100% silent recovery! Please contact me if you feel this is getting in your way. if (g.IO.ConfigErrorRecovery) @@ -10547,8 +10254,16 @@ void ImGui::ErrorRecoveryTryToRecoverState(const ImGuiErrorRecoveryState* state_ ImGuiWindow* window = g.CurrentWindow; if (window->Flags & ImGuiWindowFlags_ChildWindow) { - IM_ASSERT_USER_ERROR(0, "Missing EndChild()"); - EndChild(); + if (g.CurrentTable != NULL && g.CurrentTable->InnerWindow == g.CurrentWindow) + { + IM_ASSERT_USER_ERROR(0, "Missing EndTable()"); + EndTable(); + } + else + { + IM_ASSERT_USER_ERROR(0, "Missing EndChild()"); + EndChild(); + } } else { @@ -10586,6 +10301,11 @@ void ImGui::ErrorRecoveryTryToRecoverWindowState(const ImGuiErrorRecoveryStat IM_ASSERT_USER_ERROR(0, "Missing EndMultiSelect()"); EndMultiSelect(); } + if (window->DC.MenuBarAppending) //-V1044 + { + IM_ASSERT_USER_ERROR(0, "Missing EndMenuBar()"); + EndMenuBar(); + } while (window->DC.TreeDepth > state_in->SizeOfTreeStack) //-V1044 { IM_ASSERT_USER_ERROR(0, "Missing TreePop()"); @@ -10662,7 +10382,7 @@ bool ImGui::ErrorLog(const char* msg) // Output to tooltip if (g.IO.ConfigErrorRecoveryEnableTooltip) { - if (BeginErrorTooltip()) + if (g.WithinFrameScope && BeginErrorTooltip()) { if (g.ErrorCountCurrentFrame < 20) { @@ -10699,7 +10419,7 @@ void ImGui::ErrorCheckEndFrameFinalizeErrorTooltip() BulletText("Code should use PushID()/PopID() in loops, or append \"##xx\" to same-label identifiers!"); BulletText("Empty label e.g. Button(\"\") == same ID as parent widget/node. Use Button(\"##xx\") instead!"); //BulletText("Code intending to use duplicate ID may use e.g. PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag()"); // Not making this too visible for fear of it being abused. - BulletText("Set io.ConfigDebugDetectIdConflicts=false to disable this warning in non-programmers builds."); + BulletText("Set io.ConfigDebugHighlightIdConflicts=false to disable this warning in non-programmers builds."); Separator(); Text("(Hold CTRL to: use"); SameLine(); @@ -10773,8 +10493,8 @@ void ImGui::KeepAliveID(ImGuiID id) ImGuiContext& g = *GImGui; if (g.ActiveId == id) g.ActiveIdIsAlive = id; - if (g.ActiveIdPreviousFrame == id) - g.ActiveIdPreviousFrameIsAlive = true; + if (g.DeactivatedItemData.ID == id) + g.DeactivatedItemData.IsAlive = true; } // Declare item bounding box for clipping and interaction. @@ -10859,6 +10579,9 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu // window->DrawList->AddRect(g.LastItemData.NavRect.Min, g.LastItemData.NavRect.Max, IM_COL32(255,255,0,255)); // [DEBUG] #endif + if (id != 0 && g.DeactivatedItemData.ID == id) + g.DeactivatedItemData.ElapseFrame = g.FrameCount; + // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) if (is_rect_visible) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Visible; @@ -11218,7 +10941,7 @@ void ImGui::BeginGroup() group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive; group_data.BackupHoveredIdIsAlive = g.HoveredId != 0; group_data.BackupIsSameLine = window->DC.IsSameLine; - group_data.BackupActiveIdPreviousFrameIsAlive = g.ActiveIdPreviousFrameIsAlive; + group_data.BackupDeactivatedIdIsAlive = g.DeactivatedItemData.IsAlive; group_data.EmitItem = true; window->DC.GroupOffset.x = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffset.x; @@ -11269,11 +10992,11 @@ void ImGui::EndGroup() // Also if you grep for LastItemId you'll notice it is only used in that context. // (The two tests not the same because ActiveIdIsAlive is an ID itself, in order to be able to handle ActiveId being overwritten during the frame.) const bool group_contains_curr_active_id = (group_data.BackupActiveIdIsAlive != g.ActiveId) && (g.ActiveIdIsAlive == g.ActiveId) && g.ActiveId; - const bool group_contains_prev_active_id = (group_data.BackupActiveIdPreviousFrameIsAlive == false) && (g.ActiveIdPreviousFrameIsAlive == true); + const bool group_contains_deactivated_id = (group_data.BackupDeactivatedIdIsAlive == false) && (g.DeactivatedItemData.IsAlive == true); if (group_contains_curr_active_id) g.LastItemData.ID = g.ActiveId; - else if (group_contains_prev_active_id) - g.LastItemData.ID = g.ActiveIdPreviousFrame; + else if (group_contains_deactivated_id) + g.LastItemData.ID = g.DeactivatedItemData.ID; g.LastItemData.Rect = group_bb; // Forward Hovered flag @@ -11287,7 +11010,7 @@ void ImGui::EndGroup() // Forward Deactivated flag g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDeactivated; - if (group_contains_prev_active_id && g.ActiveId != g.ActiveIdPreviousFrame) + if (group_contains_deactivated_id) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Deactivated; g.GroupStack.pop_back(); @@ -11706,6 +11429,43 @@ ImGuiWindow* ImGui::GetTopMostAndVisiblePopupModal() return NULL; } + +// When a modal popup is open, newly created windows that want focus (i.e. are not popups and do not specify ImGuiWindowFlags_NoFocusOnAppearing) +// should be positioned behind that modal window, unless the window was created inside the modal begin-stack. +// In case of multiple stacked modals newly created window honors begin stack order and does not go below its own modal parent. +// - WindowA // FindBlockingModal() returns Modal1 +// - WindowB // .. returns Modal1 +// - Modal1 // .. returns Modal2 +// - WindowC // .. returns Modal2 +// - WindowD // .. returns Modal2 +// - Modal2 // .. returns Modal2 +// - WindowE // .. returns NULL +// Notes: +// - FindBlockingModal(NULL) == NULL is generally equivalent to GetTopMostPopupModal() == NULL. +// Only difference is here we check for ->Active/WasActive but it may be unnecessary. +ImGuiWindow* ImGui::FindBlockingModal(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (g.OpenPopupStack.Size <= 0) + return NULL; + + // Find a modal that has common parent with specified window. Specified window should be positioned behind that modal. + for (ImGuiPopupData& popup_data : g.OpenPopupStack) + { + ImGuiWindow* popup_window = popup_data.Window; + if (popup_window == NULL || !(popup_window->Flags & ImGuiWindowFlags_Modal)) + continue; + if (!popup_window->Active && !popup_window->WasActive) // Check WasActive, because this code may run before popup renders on current frame, also check Active to handle newly created windows. + continue; + if (window == NULL) // FindBlockingModal(NULL) test for if FocusWindow(NULL) is naturally possible via a mouse click. + return popup_window; + if (IsWindowWithinBeginStackOf(window, popup_window)) // Window may be over modal + continue; + return popup_window; // Place window right below first block modal + } + return NULL; +} + void ImGui::OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags) { ImGuiContext& g = *GImGui; @@ -11983,11 +11743,11 @@ void ImGui::EndPopup() NavMoveRequestTryWrapping(window, ImGuiNavMoveFlags_LoopY); // Child-popups don't need to be laid out - IM_ASSERT(g.WithinEndChild == false); + const ImGuiID backup_within_end_child_id = g.WithinEndChildID; if (window->Flags & ImGuiWindowFlags_ChildWindow) - g.WithinEndChild = true; + g.WithinEndChildID = window->ID; End(); - g.WithinEndChild = false; + g.WithinEndChildID = backup_within_end_child_id; } // Helper to open a popup if mouse button is released over the item @@ -12209,6 +11969,273 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) return window->Pos; } +//----------------------------------------------------------------------------- +// [SECTION] WINDOW FOCUS +//---------------------------------------------------------------------------- +// - SetWindowFocus() +// - SetNextWindowFocus() +// - IsWindowFocused() +// - UpdateWindowInFocusOrderList() [Internal] +// - BringWindowToFocusFront() [Internal] +// - BringWindowToDisplayFront() [Internal] +// - BringWindowToDisplayBack() [Internal] +// - BringWindowToDisplayBehind() [Internal] +// - FindWindowDisplayIndex() [Internal] +// - FocusWindow() [Internal] +// - FocusTopMostWindowUnderOne() [Internal] +//----------------------------------------------------------------------------- + +void ImGui::SetWindowFocus() +{ + FocusWindow(GImGui->CurrentWindow); +} + +void ImGui::SetWindowFocus(const char* name) +{ + if (name) + { + if (ImGuiWindow* window = FindWindowByName(name)) + FocusWindow(window); + } + else + { + FocusWindow(NULL); + } +} + +void ImGui::SetNextWindowFocus() +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasFocus; +} + +// Similar to IsWindowHovered() +bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* ref_window = g.NavWindow; + ImGuiWindow* cur_window = g.CurrentWindow; + + if (ref_window == NULL) + return false; + if (flags & ImGuiFocusedFlags_AnyWindow) + return true; + + IM_ASSERT(cur_window); // Not inside a Begin()/End() + const bool popup_hierarchy = (flags & ImGuiFocusedFlags_NoPopupHierarchy) == 0; + if (flags & ImGuiHoveredFlags_RootWindow) + cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy); + + if (flags & ImGuiHoveredFlags_ChildWindows) + return IsWindowChildOf(ref_window, cur_window, popup_hierarchy); + else + return (ref_window == cur_window); +} + +static int ImGui::FindWindowFocusIndex(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IM_UNUSED(g); + int order = window->FocusOrder; + IM_ASSERT(window->RootWindow == window); // No child window (not testing _ChildWindow because of docking) + IM_ASSERT(g.WindowsFocusOrder[order] == window); + return order; +} + +static void ImGui::UpdateWindowInFocusOrderList(ImGuiWindow* window, bool just_created, ImGuiWindowFlags new_flags) +{ + ImGuiContext& g = *GImGui; + + const bool new_is_explicit_child = (new_flags & ImGuiWindowFlags_ChildWindow) != 0 && ((new_flags & ImGuiWindowFlags_Popup) == 0 || (new_flags & ImGuiWindowFlags_ChildMenu) != 0); + const bool child_flag_changed = new_is_explicit_child != window->IsExplicitChild; + if ((just_created || child_flag_changed) && !new_is_explicit_child) + { + IM_ASSERT(!g.WindowsFocusOrder.contains(window)); + g.WindowsFocusOrder.push_back(window); + window->FocusOrder = (short)(g.WindowsFocusOrder.Size - 1); + } + else if (!just_created && child_flag_changed && new_is_explicit_child) + { + IM_ASSERT(g.WindowsFocusOrder[window->FocusOrder] == window); + for (int n = window->FocusOrder + 1; n < g.WindowsFocusOrder.Size; n++) + g.WindowsFocusOrder[n]->FocusOrder--; + g.WindowsFocusOrder.erase(g.WindowsFocusOrder.Data + window->FocusOrder); + window->FocusOrder = -1; + } + window->IsExplicitChild = new_is_explicit_child; +} + +void ImGui::BringWindowToFocusFront(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(window == window->RootWindow); + + const int cur_order = window->FocusOrder; + IM_ASSERT(g.WindowsFocusOrder[cur_order] == window); + if (g.WindowsFocusOrder.back() == window) + return; + + const int new_order = g.WindowsFocusOrder.Size - 1; + for (int n = cur_order; n < new_order; n++) + { + g.WindowsFocusOrder[n] = g.WindowsFocusOrder[n + 1]; + g.WindowsFocusOrder[n]->FocusOrder--; + IM_ASSERT(g.WindowsFocusOrder[n]->FocusOrder == n); + } + g.WindowsFocusOrder[new_order] = window; + window->FocusOrder = (short)new_order; +} + +// Note technically focus related but rather adjacent and close to BringWindowToFocusFront() +void ImGui::BringWindowToDisplayFront(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* current_front_window = g.Windows.back(); + if (current_front_window == window || current_front_window->RootWindow == window) // Cheap early out (could be better) + return; + for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the top-most window + if (g.Windows[i] == window) + { + memmove(&g.Windows[i], &g.Windows[i + 1], (size_t)(g.Windows.Size - i - 1) * sizeof(ImGuiWindow*)); + g.Windows[g.Windows.Size - 1] = window; + break; + } +} + +void ImGui::BringWindowToDisplayBack(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (g.Windows[0] == window) + return; + for (int i = 0; i < g.Windows.Size; i++) + if (g.Windows[i] == window) + { + memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow*)); + g.Windows[0] = window; + break; + } +} + +void ImGui::BringWindowToDisplayBehind(ImGuiWindow* window, ImGuiWindow* behind_window) +{ + IM_ASSERT(window != NULL && behind_window != NULL); + ImGuiContext& g = *GImGui; + window = window->RootWindow; + behind_window = behind_window->RootWindow; + int pos_wnd = FindWindowDisplayIndex(window); + int pos_beh = FindWindowDisplayIndex(behind_window); + if (pos_wnd < pos_beh) + { + size_t copy_bytes = (pos_beh - pos_wnd - 1) * sizeof(ImGuiWindow*); + memmove(&g.Windows.Data[pos_wnd], &g.Windows.Data[pos_wnd + 1], copy_bytes); + g.Windows[pos_beh - 1] = window; + } + else + { + size_t copy_bytes = (pos_wnd - pos_beh) * sizeof(ImGuiWindow*); + memmove(&g.Windows.Data[pos_beh + 1], &g.Windows.Data[pos_beh], copy_bytes); + g.Windows[pos_beh] = window; + } +} + +int ImGui::FindWindowDisplayIndex(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + return g.Windows.index_from_ptr(g.Windows.find(window)); +} + +// Moving window to front of display and set focus (which happens to be back of our sorted list) +void ImGui::FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags) +{ + ImGuiContext& g = *GImGui; + + // Modal check? + if ((flags & ImGuiFocusRequestFlags_UnlessBelowModal) && (g.NavWindow != window)) // Early out in common case. + if (ImGuiWindow* blocking_modal = FindBlockingModal(window)) + { + // This block would typically be reached in two situations: + // - API call to FocusWindow() with a window under a modal and ImGuiFocusRequestFlags_UnlessBelowModal flag. + // - User clicking on void or anything behind a modal while a modal is open (window == NULL) + IMGUI_DEBUG_LOG_FOCUS("[focus] FocusWindow(\"%s\", UnlessBelowModal): prevented by \"%s\".\n", window ? window->Name : "", blocking_modal->Name); + if (window && window == window->RootWindow && (window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0) + BringWindowToDisplayBehind(window, blocking_modal); // Still bring right under modal. (FIXME: Could move in focus list too?) + ClosePopupsOverWindow(GetTopMostPopupModal(), false); // Note how we need to use GetTopMostPopupModal() aad NOT blocking_modal, to handle nested modals + return; + } + + // Find last focused child (if any) and focus it instead. + if ((flags & ImGuiFocusRequestFlags_RestoreFocusedChild) && window != NULL) + window = NavRestoreLastChildNavWindow(window); + + // Apply focus + if (g.NavWindow != window) + { + SetNavWindow(window); + if (window && g.NavHighlightItemUnderNav) + g.NavMousePosDirty = true; + g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId + g.NavLayer = ImGuiNavLayer_Main; + SetNavFocusScope(window ? window->NavRootFocusScopeId : 0); + g.NavIdIsAlive = false; + g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid; + + // Close popups if any + ClosePopupsOverWindow(window, false); + } + + // Move the root window to the top of the pile + IM_ASSERT(window == NULL || window->RootWindow != NULL); + ImGuiWindow* focus_front_window = window ? window->RootWindow : NULL; // NB: In docking branch this is window->RootWindowDockStop + ImGuiWindow* display_front_window = window ? window->RootWindow : NULL; + + // Steal active widgets. Some of the cases it triggers includes: + // - Focus a window while an InputText in another window is active, if focus happens before the old InputText can run. + // - When using Nav to activate menu items (due to timing of activating on press->new window appears->losing ActiveId) + if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != focus_front_window) + if (!g.ActiveIdNoClearOnFocusLoss) + ClearActiveID(); + + // Passing NULL allow to disable keyboard focus + if (!window) + return; + + // Bring to front + BringWindowToFocusFront(focus_front_window); + if (((window->Flags | display_front_window->Flags) & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0) + BringWindowToDisplayFront(display_front_window); +} + +void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWindow* ignore_window, ImGuiViewport* filter_viewport, ImGuiFocusRequestFlags flags) +{ + ImGuiContext& g = *GImGui; + IM_UNUSED(filter_viewport); // Unused in master branch. + int start_idx = g.WindowsFocusOrder.Size - 1; + if (under_this_window != NULL) + { + // Aim at root window behind us, if we are in a child window that's our own root (see #4640) + int offset = -1; + while (under_this_window->Flags & ImGuiWindowFlags_ChildWindow) + { + under_this_window = under_this_window->ParentWindow; + offset = 0; + } + start_idx = FindWindowFocusIndex(under_this_window) + offset; + } + for (int i = start_idx; i >= 0; i--) + { + // We may later decide to test for different NoXXXInputs based on the active navigation input (mouse vs nav) but that may feel more confusing to the user. + ImGuiWindow* window = g.WindowsFocusOrder[i]; + if (window == ignore_window || !window->WasActive) + continue; + if ((window->Flags & (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) != (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) + { + FocusWindow(window, flags); + return; + } + } + FocusWindow(NULL, flags); +} + //----------------------------------------------------------------------------- // [SECTION] KEYBOARD/GAMEPAD NAVIGATION //----------------------------------------------------------------------------- @@ -13000,7 +13027,7 @@ static void ImGui::NavUpdate() { // *Fallback* manual-scroll with Nav directional keys when window has no navigable item ImGuiWindow* window = g.NavWindow; - const float scroll_speed = IM_ROUND(window->CalcFontSize() * 100 * io.DeltaTime); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported. + const float scroll_speed = IM_ROUND(window->FontRefSize * 100 * io.DeltaTime); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported. const ImGuiDir move_dir = g.NavMoveDir; if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavWindowHasScrollY && move_dir != ImGuiDir_None) { @@ -13190,8 +13217,8 @@ void ImGui::NavUpdateCreateMoveRequest() if ((clamp_x || clamp_y) && !inner_rect_rel.Contains(window->NavRectRel[g.NavLayer])) { IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequest: clamp NavRectRel for gamepad move\n"); - float pad_x = ImMin(inner_rect_rel.GetWidth(), window->CalcFontSize() * 0.5f); - float pad_y = ImMin(inner_rect_rel.GetHeight(), window->CalcFontSize() * 0.5f); // Terrible approximation for the intent of starting navigation from first fully visible item + float pad_x = ImMin(inner_rect_rel.GetWidth(), window->FontRefSize * 0.5f); + float pad_y = ImMin(inner_rect_rel.GetHeight(), window->FontRefSize * 0.5f); // Terrible approximation for the intent of starting navigation from first fully visible item inner_rect_rel.Min.x = clamp_x ? (inner_rect_rel.Min.x + pad_x) : -FLT_MAX; inner_rect_rel.Max.x = clamp_x ? (inner_rect_rel.Max.x - pad_x) : +FLT_MAX; inner_rect_rel.Min.y = clamp_y ? (inner_rect_rel.Min.y + pad_y) : -FLT_MAX; @@ -13449,7 +13476,7 @@ static float ImGui::NavUpdatePageUpPageDown() else { ImRect& nav_rect_rel = window->NavRectRel[g.NavLayer]; - const float page_offset_y = ImMax(0.0f, window->InnerRect.GetHeight() - window->CalcFontSize() * 1.0f + nav_rect_rel.GetHeight()); + const float page_offset_y = ImMax(0.0f, window->InnerRect.GetHeight() - window->FontRefSize * 1.0f + nav_rect_rel.GetHeight()); float nav_scoring_rect_offset_y = 0.0f; if (IsKeyPressed(ImGuiKey_PageUp, true)) { @@ -13565,14 +13592,12 @@ static void ImGui::NavUpdateCreateWrappingRequest() NavMoveRequestForward(g.NavMoveDir, clip_dir, move_flags, g.NavMoveScrollFlags); } -static int ImGui::FindWindowFocusIndex(ImGuiWindow* window) +// Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext) +// Note that NoNavFocus makes the window not reachable with CTRL+TAB but it can still be focused with mouse or programmatically. +// If you want a window to never be focused, you may use the e.g. NoInputs flag. +bool ImGui::IsWindowNavFocusable(ImGuiWindow* window) { - ImGuiContext& g = *GImGui; - IM_UNUSED(g); - int order = window->FocusOrder; - IM_ASSERT(window->RootWindow == window); // No child window (not testing _ChildWindow because of docking) - IM_ASSERT(g.WindowsFocusOrder[order] == window); - return order; + return window->WasActive && window == window->RootWindow && !(window->Flags & ImGuiWindowFlags_NoNavFocus); } static ImGuiWindow* FindWindowNavFocusable(int i_start, int i_stop, int dir) // FIXME-OPT O(N) @@ -13629,7 +13654,7 @@ static void ImGui::NavUpdateWindowing() // Start CTRL+Tab or Square+L/R window selection // (g.ConfigNavWindowingKeyNext/g.ConfigNavWindowingKeyPrev defaults are ImGuiMod_Ctrl|ImGuiKey_Tab and ImGuiMod_Ctrl|ImGuiMod_Shift|ImGuiKey_Tab) - const ImGuiID owner_id = ImHashStr("###NavUpdateWindowing"); + const ImGuiID owner_id = ImHashStr("##NavUpdateWindowing"); const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; const bool keyboard_next_window = allow_windowing && g.ConfigNavWindowingKeyNext && Shortcut(g.ConfigNavWindowingKeyNext, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways, owner_id); @@ -13696,15 +13721,16 @@ static void ImGui::NavUpdateWindowing() // Keyboard: Press and Release ALT to toggle menu layer const ImGuiKey windowing_toggle_keys[] = { ImGuiKey_LeftAlt, ImGuiKey_RightAlt }; bool windowing_toggle_layer_start = false; - for (ImGuiKey windowing_toggle_key : windowing_toggle_keys) - if (nav_keyboard_active && IsKeyPressed(windowing_toggle_key, 0, ImGuiKeyOwner_NoOwner)) - { - windowing_toggle_layer_start = true; - g.NavWindowingToggleLayer = true; - g.NavWindowingToggleKey = windowing_toggle_key; - g.NavInputSource = ImGuiInputSource_Keyboard; - break; - } + if (g.NavWindow != NULL && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + for (ImGuiKey windowing_toggle_key : windowing_toggle_keys) + if (nav_keyboard_active && IsKeyPressed(windowing_toggle_key, 0, ImGuiKeyOwner_NoOwner)) + { + windowing_toggle_layer_start = true; + g.NavWindowingToggleLayer = true; + g.NavWindowingToggleKey = windowing_toggle_key; + g.NavInputSource = ImGuiInputSource_Keyboard; + break; + } if (g.NavWindowingToggleLayer && g.NavInputSource == ImGuiInputSource_Keyboard) { // We cancel toggling nav layer when any text has been typed (generally while holding Alt). (See #370) @@ -13828,12 +13854,12 @@ void ImGui::NavUpdateWindowingOverlay() return; if (g.NavWindowingListWindow == NULL) - g.NavWindowingListWindow = FindWindowByName("###NavWindowingList"); + g.NavWindowingListWindow = FindWindowByName("##NavWindowingOverlay"); const ImGuiViewport* viewport = GetMainViewport(); SetNextWindowSizeConstraints(ImVec2(viewport->Size.x * 0.20f, viewport->Size.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX)); SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f); - Begin("###NavWindowingList", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings); + Begin("##NavWindowingOverlay", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings); if (g.ContextName[0] != 0) SeparatorText(g.ContextName); for (int n = g.WindowsFocusOrder.Size - 1; n >= 0; n--) @@ -13851,7 +13877,6 @@ void ImGui::NavUpdateWindowingOverlay() PopStyleVar(); } - //----------------------------------------------------------------------------- // [SECTION] DRAG AND DROP //----------------------------------------------------------------------------- @@ -14317,15 +14342,17 @@ void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char* } // Start logging/capturing text output -void ImGui::LogBegin(ImGuiLogType type, int auto_open_depth) +void ImGui::LogBegin(ImGuiLogFlags flags, int auto_open_depth) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; IM_ASSERT(g.LogEnabled == false); - IM_ASSERT(g.LogFile == NULL); - IM_ASSERT(g.LogBuffer.empty()); + IM_ASSERT(g.LogFile == NULL && g.LogBuffer.empty()); + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiLogFlags_OutputMask_)); // Check that only 1 type flag is used + g.LogEnabled = g.ItemUnclipByLog = true; - g.LogType = type; + g.LogFlags = flags; + g.LogWindow = window; g.LogNextPrefix = g.LogNextSuffix = NULL; g.LogDepthRef = window->DC.TreeDepth; g.LogDepthToExpand = ((auto_open_depth >= 0) ? auto_open_depth : g.LogDepthToExpandDefault); @@ -14348,7 +14375,7 @@ void ImGui::LogToTTY(int auto_open_depth) return; IM_UNUSED(auto_open_depth); #ifndef IMGUI_DISABLE_TTY_FUNCTIONS - LogBegin(ImGuiLogType_TTY, auto_open_depth); + LogBegin(ImGuiLogFlags_OutputTTY, auto_open_depth); g.LogFile = stdout; #endif } @@ -14374,7 +14401,7 @@ void ImGui::LogToFile(int auto_open_depth, const char* filename) return; } - LogBegin(ImGuiLogType_File, auto_open_depth); + LogBegin(ImGuiLogFlags_OutputFile, auto_open_depth); g.LogFile = f; } @@ -14384,7 +14411,7 @@ void ImGui::LogToClipboard(int auto_open_depth) ImGuiContext& g = *GImGui; if (g.LogEnabled) return; - LogBegin(ImGuiLogType_Clipboard, auto_open_depth); + LogBegin(ImGuiLogFlags_OutputClipboard, auto_open_depth); } void ImGui::LogToBuffer(int auto_open_depth) @@ -14392,7 +14419,7 @@ void ImGui::LogToBuffer(int auto_open_depth) ImGuiContext& g = *GImGui; if (g.LogEnabled) return; - LogBegin(ImGuiLogType_Buffer, auto_open_depth); + LogBegin(ImGuiLogFlags_OutputBuffer, auto_open_depth); } void ImGui::LogFinish() @@ -14402,29 +14429,29 @@ void ImGui::LogFinish() return; LogText(IM_NEWLINE); - switch (g.LogType) + switch (g.LogFlags & ImGuiLogFlags_OutputMask_) { - case ImGuiLogType_TTY: + case ImGuiLogFlags_OutputTTY: #ifndef IMGUI_DISABLE_TTY_FUNCTIONS fflush(g.LogFile); #endif break; - case ImGuiLogType_File: + case ImGuiLogFlags_OutputFile: ImFileClose(g.LogFile); break; - case ImGuiLogType_Buffer: + case ImGuiLogFlags_OutputBuffer: break; - case ImGuiLogType_Clipboard: + case ImGuiLogFlags_OutputClipboard: if (!g.LogBuffer.empty()) SetClipboardText(g.LogBuffer.begin()); break; - case ImGuiLogType_None: + default: IM_ASSERT(0); break; } g.LogEnabled = g.ItemUnclipByLog = false; - g.LogType = ImGuiLogType_None; + g.LogFlags = ImGuiLogFlags_None; g.LogFile = NULL; g.LogBuffer.clear(); } @@ -14458,7 +14485,6 @@ void ImGui::LogButtons() LogToClipboard(); } - //----------------------------------------------------------------------------- // [SECTION] SETTINGS //----------------------------------------------------------------------------- @@ -14818,7 +14844,6 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl } } - //----------------------------------------------------------------------------- // [SECTION] LOCALIZATION //----------------------------------------------------------------------------- @@ -14830,12 +14855,12 @@ void ImGui::LocalizeRegisterEntries(const ImGuiLocEntry* entries, int count) g.LocalizationTable[entries[n].Key] = entries[n].Text; } - //----------------------------------------------------------------------------- // [SECTION] VIEWPORTS, PLATFORM WINDOWS //----------------------------------------------------------------------------- // - GetMainViewport() // - SetWindowViewport() [Internal] +// - ScaleWindowsInViewport() [Internal] // - UpdateViewportsNewFrame() [Internal] // (this section is more complete in the 'docking' branch) //----------------------------------------------------------------------------- @@ -14851,6 +14876,24 @@ void ImGui::SetWindowViewport(ImGuiWindow* window, ImGuiViewportP* viewport) window->Viewport = viewport; } +static void ScaleWindow(ImGuiWindow* window, float scale) +{ + ImVec2 origin = window->Viewport->Pos; + window->Pos = ImFloor((window->Pos - origin) * scale + origin); + window->Size = ImTrunc(window->Size * scale); + window->SizeFull = ImTrunc(window->SizeFull * scale); + window->ContentSize = ImTrunc(window->ContentSize * scale); +} + +// Scale all windows (position, size). Use when e.g. changing DPI. (This is a lossy operation!) +void ImGui::ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale) +{ + ImGuiContext& g = *GImGui; + for (ImGuiWindow* window : g.Windows) + if (window->Viewport == viewport) + ScaleWindow(window, scale); +} + // Update viewports and monitor infos static void ImGui::UpdateViewportsNewFrame() { @@ -15298,6 +15341,17 @@ void ImGui::UpdateDebugToolFlashStyleColor() DebugFlashStyleColorStop(); } +static const char* FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTextureID tex_id) +{ + union { void* ptr; int integer; } tex_id_opaque; + memcpy(&tex_id_opaque, &tex_id, ImMin(sizeof(void*), sizeof(tex_id))); + if (sizeof(tex_id) >= sizeof(void*)) + ImFormatString(buf, buf_size, "0x%p", tex_id_opaque.ptr); + else + ImFormatString(buf, buf_size, "0x%04X", tex_id_opaque.integer); + return buf; +} + // Avoid naming collision with imgui_demo.cpp's HelpMarker() for unity builds. static void MetricsHelpMarker(const char* desc) { @@ -15722,18 +15776,11 @@ void ImGui::ShowMetricsWindow(bool* p_open) { Text("KEYBOARD/GAMEPAD/MOUSE KEYS"); { - // We iterate both legacy native range and named ImGuiKey ranges, which is a little odd but this allows displaying the data for old/new backends. // User code should never have to go through such hoops! You can generally iterate between ImGuiKey_NamedKey_BEGIN and ImGuiKey_NamedKey_END. Indent(); -#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO - struct funcs { static bool IsLegacyNativeDupe(ImGuiKey) { return false; } }; -#else - struct funcs { static bool IsLegacyNativeDupe(ImGuiKey key) { return key >= 0 && key < 512 && GetIO().KeyMap[key] != -1; } }; // Hide Native<>ImGuiKey duplicates when both exists in the array - //Text("Legacy raw:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key++) { if (io.KeysDown[key]) { SameLine(); Text("\"%s\" %d", GetKeyName(key), key); } } -#endif - Text("Keys down:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key) || !IsKeyDown(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); SameLine(); Text("(%.02f)", GetKeyData(key)->DownDuration); } - Text("Keys pressed:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key) || !IsKeyPressed(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); } - Text("Keys released:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key) || !IsKeyReleased(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); } + Text("Keys down:"); for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { if (!IsKeyDown(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); SameLine(); Text("(%.02f)", GetKeyData(key)->DownDuration); } + Text("Keys pressed:"); for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { if (!IsKeyPressed(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); } + Text("Keys released:"); for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { if (!IsKeyReleased(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); } Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : ""); Text("Chars queue:"); for (int i = 0; i < io.InputQueueCharacters.Size; i++) { ImWchar c = io.InputQueueCharacters[i]; SameLine(); Text("\'%c\' (0x%04X)", (c > ' ' && c <= 255) ? (char)c : '?', c); } // FIXME: We should convert 'c' to UTF-8 here but the functions are not public. DebugRenderKeyboardPreview(GetWindowDrawList()); @@ -15993,16 +16040,6 @@ void ImGui::DebugNodeColumns(ImGuiOldColumns* columns) TreePop(); } -static void FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTextureID tex_id) -{ - union { void* ptr; int integer; } tex_id_opaque; - memcpy(&tex_id_opaque, &tex_id, ImMin(sizeof(void*), sizeof(tex_id))); - if (sizeof(tex_id) >= sizeof(void*)) - ImFormatString(buf, buf_size, "0x%p", tex_id_opaque.ptr); - else - ImFormatString(buf, buf_size, "0x%04X", tex_id_opaque.integer); -} - // [DEBUG] Display contents of ImDrawList void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, const ImDrawList* draw_list, const char* label) { @@ -16132,18 +16169,24 @@ void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, co // [DEBUG] Display details for a single font, called by ShowStyleEditor(). void ImGui::DebugNodeFont(ImFont* font) { - bool opened = TreeNode(font, "Font: \"%s\"\n%.2f px, %d glyphs, %d file(s)", + bool opened = TreeNode(font, "Font: \"%s\": %.2f px, %d glyphs, %d sources(s)", font->ConfigData ? font->ConfigData[0].Name : "", font->FontSize, font->Glyphs.Size, font->ConfigDataCount); - SameLine(); - if (SmallButton("Set as default")) - GetIO().FontDefault = font; - if (!opened) - return; // Display preview text + if (!opened) + Indent(); + Indent(); PushFont(font); Text("The quick brown fox jumps over the lazy dog"); PopFont(); + if (!opened) + { + Unindent(); + Unindent(); + return; + } + if (SmallButton("Set as default")) + GetIO().FontDefault = font; // Display details SetNextItemWidth(GetFontSize() * 8); @@ -16162,62 +16205,69 @@ void ImGui::DebugNodeFont(ImFont* font) Text("Texture Area: about %d px ~%dx%d px", font->MetricsTotalSurface, surface_sqrt, surface_sqrt); for (int config_i = 0; config_i < font->ConfigDataCount; config_i++) if (font->ConfigData) - if (const ImFontConfig* cfg = &font->ConfigData[config_i]) - BulletText("Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", - config_i, cfg->Name, cfg->OversampleH, cfg->OversampleV, cfg->PixelSnapH, cfg->GlyphOffset.x, cfg->GlyphOffset.y); + { + const ImFontConfig* cfg = &font->ConfigData[config_i]; + int oversample_h, oversample_v; + ImFontAtlasBuildGetOversampleFactors(cfg, &oversample_h, &oversample_v); + BulletText("Input %d: \'%s\', Oversample: (%d=>%d,%d=>%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", + config_i, cfg->Name, cfg->OversampleH, oversample_h, cfg->OversampleV, oversample_v, cfg->PixelSnapH, cfg->GlyphOffset.x, cfg->GlyphOffset.y); + } // Display all glyphs of the fonts in separate pages of 256 characters - if (TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) { - ImDrawList* draw_list = GetWindowDrawList(); - const ImU32 glyph_col = GetColorU32(ImGuiCol_Text); - const float cell_size = font->FontSize * 1; - const float cell_spacing = GetStyle().ItemSpacing.y; - for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256) + if (TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) { - // Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k) - // This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT - // is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here) - if (!(base & 4095) && font->IsGlyphRangeUnused(base, base + 4095)) + ImDrawList* draw_list = GetWindowDrawList(); + const ImU32 glyph_col = GetColorU32(ImGuiCol_Text); + const float cell_size = font->FontSize * 1; + const float cell_spacing = GetStyle().ItemSpacing.y; + for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256) { - base += 4096 - 256; - continue; - } - - int count = 0; - for (unsigned int n = 0; n < 256; n++) - if (font->FindGlyphNoFallback((ImWchar)(base + n))) - count++; - if (count <= 0) - continue; - if (!TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base + 255, count, count > 1 ? "glyphs" : "glyph")) - continue; - - // Draw a 16x16 grid of glyphs - ImVec2 base_pos = GetCursorScreenPos(); - for (unsigned int n = 0; n < 256; n++) - { - // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions - // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. - ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); - ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); - const ImFontGlyph* glyph = font->FindGlyphNoFallback((ImWchar)(base + n)); - draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50)); - if (!glyph) - continue; - font->RenderChar(draw_list, cell_size, cell_p1, glyph_col, (ImWchar)(base + n)); - if (IsMouseHoveringRect(cell_p1, cell_p2) && BeginTooltip()) + // Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k) + // This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT + // is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here) + if (!(base & 8191) && font->IsGlyphRangeUnused(base, base + 8191)) { - DebugNodeFontGlyph(font, glyph); - EndTooltip(); + base += 8192 - 256; + continue; } + + int count = 0; + for (unsigned int n = 0; n < 256; n++) + if (font->FindGlyphNoFallback((ImWchar)(base + n))) + count++; + if (count <= 0) + continue; + if (!TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base + 255, count, count > 1 ? "glyphs" : "glyph")) + continue; + + // Draw a 16x16 grid of glyphs + ImVec2 base_pos = GetCursorScreenPos(); + for (unsigned int n = 0; n < 256; n++) + { + // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions + // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. + ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); + ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); + const ImFontGlyph* glyph = font->FindGlyphNoFallback((ImWchar)(base + n)); + draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50)); + if (!glyph) + continue; + font->RenderChar(draw_list, cell_size, cell_p1, glyph_col, (ImWchar)(base + n)); + if (IsMouseHoveringRect(cell_p1, cell_p2) && BeginTooltip()) + { + DebugNodeFontGlyph(font, glyph); + EndTooltip(); + } + } + Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16)); + TreePop(); } - Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16)); TreePop(); } - TreePop(); } TreePop(); + Unindent(); } void ImGui::DebugNodeFontGlyph(ImFont*, const ImFontGlyph* glyph) @@ -16351,7 +16401,7 @@ void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label) for (int layer = 0; layer < ImGuiNavLayer_COUNT; layer++) { ImRect r = window->NavRectRel[layer]; - if (r.Min.x >= r.Max.y && r.Min.y >= r.Max.y) + if (r.Min.x >= r.Max.x && r.Min.y >= r.Max.y) BulletText("NavLastIds[%d]: 0x%08X", layer, window->NavLastIds[layer]); else BulletText("NavLastIds[%d]: 0x%08X at +(%.1f,%.1f)(%.1f,%.1f)", layer, window->NavLastIds[layer], r.Min.x, r.Min.y, r.Max.x, r.Max.y); @@ -16504,6 +16554,7 @@ void ImGui::ShowDebugLogWindow(bool* p_open) ShowDebugLogFlag("Clipper", ImGuiDebugLogFlags_EventClipper); ShowDebugLogFlag("Focus", ImGuiDebugLogFlags_EventFocus); ShowDebugLogFlag("IO", ImGuiDebugLogFlags_EventIO); + //ShowDebugLogFlag("Font", ImGuiDebugLogFlags_EventFont); ShowDebugLogFlag("Nav", ImGuiDebugLogFlags_EventNav); ShowDebugLogFlag("Popup", ImGuiDebugLogFlags_EventPopup); ShowDebugLogFlag("Selection", ImGuiDebugLogFlags_EventSelection); @@ -16556,7 +16607,7 @@ void ImGui::ShowDebugLogWindow(bool* p_open) void ImGui::DebugTextUnformattedWithLocateItem(const char* line_begin, const char* line_end) { TextUnformatted(line_begin, line_end); - if (!IsItemHovered()) + if (!IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) return; ImGuiContext& g = *GImGui; ImRect text_rect = g.LastItemData.Rect; diff --git a/Amalgam/include/ImGui/imgui.h b/Amalgam/include/ImGui/imgui.h index 95d0f009..ba0b102d 100644 --- a/Amalgam/include/ImGui/imgui.h +++ b/Amalgam/include/ImGui/imgui.h @@ -1,4 +1,4 @@ -// dear imgui, v1.91.4 +// dear imgui, v1.91.8 // (headers) // Help: @@ -28,8 +28,8 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') -#define IMGUI_VERSION "1.91.4" -#define IMGUI_VERSION_NUM 19140 +#define IMGUI_VERSION "1.91.8" +#define IMGUI_VERSION_NUM 19180 #define IMGUI_HAS_TABLE /* @@ -130,15 +130,17 @@ Index of this file: #pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' #endif #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' -#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast #pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe -#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant #pragma clang diagnostic ignored "-Wreserved-identifier" // warning: identifier '_Xxx' is reserved because it starts with '_' followed by a capital letter #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access +#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type #elif defined(__GNUC__) #pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind -#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #endif //----------------------------------------------------------------------------- @@ -156,7 +158,7 @@ typedef unsigned int ImU32; // 32-bit unsigned integer (often used to st typedef signed long long ImS64; // 64-bit signed integer typedef unsigned long long ImU64; // 64-bit unsigned integer -// Forward declarations +// Forward declarations: ImDrawList, ImFontAtlas layer struct ImDrawChannel; // Temporary storage to output draw commands out of order, used by ImDrawListSplitter and ImDrawList::ChannelsSplit() struct ImDrawCmd; // A single draw command within a parent ImDrawList (generally maps to 1 GPU draw call, unless it is a callback) struct ImDrawData; // All draw command lists required to render the frame + pos/size coordinates to use for the projection matrix. @@ -171,6 +173,8 @@ struct ImFontConfig; // Configuration data when adding a font or struct ImFontGlyph; // A single font glyph (code point + coordinates within in ImFontAtlas + offset) struct ImFontGlyphRangesBuilder; // Helper to build glyph ranges from text/string data struct ImColor; // Helper functions to create a color that can be converted to either u32 or float4 (*OBSOLETE* please avoid using) + +// Forward declarations: ImGui layer struct ImGuiContext; // Dear ImGui context (opaque structure, unless including imgui_internal.h) struct ImGuiIO; // Main configuration and I/O between your application and ImGui (also see: ImGuiPlatformIO) struct ImGuiInputTextCallbackData; // Shared state of InputText() when using custom ImGuiInputTextCallback (rare/advanced use) @@ -563,6 +567,7 @@ namespace ImGui // - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples // - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above. // - Note that Image() may add +2.0f to provided size if a border is visible, ImageButton() adds style.FramePadding*2.0f to provided size. + // - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& tint_col = ImVec4(1, 1, 1, 1), const ImVec4& border_col = ImVec4(0, 0, 0, 0)); IMGUI_API bool ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); @@ -690,6 +695,7 @@ namespace ImGui // Widgets: List Boxes // - This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label. + // - If you don't need a label you can probably simply use BeginChild() with the ImGuiChildFlags_FrameStyle flag for the same result. // - You can submit contents and manage your selection state however you want it, by creating e.g. Selectable() or any other items. // - The simplified/old ListBox() api are helpers over BeginListBox()/EndListBox() which are kept available for convenience purpose. This is analoguous to how Combos are created. // - Choose frame width: size.x > 0.0f: custom / size.x < 0.0f or -FLT_MIN: right-align / size.x = 0.0f (default): use current ItemWidth @@ -961,9 +967,8 @@ namespace ImGui // Inputs Utilities: Keyboard/Mouse/Gamepad // - the ImGuiKey enum contains all possible keyboard, mouse and gamepad inputs (e.g. ImGuiKey_A, ImGuiKey_MouseLeft, ImGuiKey_GamepadDpadUp...). - // - before v1.87, we used ImGuiKey to carry native/user indices as defined by each backends. About use of those legacy ImGuiKey values: - // - without IMGUI_DISABLE_OBSOLETE_KEYIO (legacy support): you can still use your legacy native/user indices (< 512) according to how your backend/engine stored them in io.KeysDown[], but need to cast them to ImGuiKey. - // - with IMGUI_DISABLE_OBSOLETE_KEYIO (this is the way forward): any use of ImGuiKey will assert with key < 512. GetKeyIndex() is pass-through and therefore deprecated (gone if IMGUI_DISABLE_OBSOLETE_KEYIO is defined). + // - (legacy: before v1.87, we used ImGuiKey to carry native/user indices as defined by each backends. This was obsoleted in 1.87 (2022-02) and completely removed in 1.91.5 (2024-11). See https://github.com/ocornut/imgui/issues/4921) + // - (legacy: any use of ImGuiKey will assert when key < 512 to detect passing legacy native/user indices) IMGUI_API bool IsKeyDown(ImGuiKey key); // is key being held. IMGUI_API bool IsKeyPressed(ImGuiKey key, bool repeat = true); // was key pressed (went from !Down to Down)? if repeat=true, uses io.KeyRepeatDelay / KeyRepeatRate IMGUI_API bool IsKeyReleased(ImGuiKey key); // was key released (went from Down to !Down)? @@ -1006,6 +1011,7 @@ namespace ImGui IMGUI_API bool IsMouseClicked(ImGuiMouseButton button, bool repeat = false); // did mouse button clicked? (went from !Down to Down). Same as GetMouseClickedCount() == 1. IMGUI_API bool IsMouseReleased(ImGuiMouseButton button); // did mouse button released? (went from Down to !Down) IMGUI_API bool IsMouseDoubleClicked(ImGuiMouseButton button); // did mouse button double-clicked? Same as GetMouseClickedCount() == 2. (note that a double-click will also report IsMouseClicked() == true) + IMGUI_API bool IsMouseReleasedWithDelay(ImGuiMouseButton button, float delay); // delayed mouse release (use very sparingly!). Generally used with 'delay >= io.MouseDoubleClickTime' + combined with a 'io.MouseClickedLastCount==1' test. This is a very rarely used UI idiom, but some apps use this: e.g. MS Explorer single click on an icon to rename. IMGUI_API int GetMouseClickedCount(ImGuiMouseButton button); // return the number of successive mouse-clicks at the time where a click happen (otherwise 0). IMGUI_API bool IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip = true);// is mouse hovering given bounding rect (in screen space). clipped by current clipping settings, but disregarding of other consideration of focus/window ordering/popup-block. IMGUI_API bool IsMousePosValid(const ImVec2* mouse_pos = NULL); // by convention we use (-FLT_MAX,-FLT_MAX) to denote that there is no mouse available @@ -1096,8 +1102,8 @@ enum ImGuiWindowFlags_ // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiWindowFlags_NavFlattened = 1 << 29, // Obsoleted in 1.90.9: Use ImGuiChildFlags_NavFlattened in BeginChild() call. ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 30, // Obsoleted in 1.90.0: Use ImGuiChildFlags_AlwaysUseWindowPadding in BeginChild() call. - ImGuiWindowFlags_NavFlattened = 1 << 31, // Obsoleted in 1.90.9: Use ImGuiChildFlags_NavFlattened in BeginChild() call. #endif }; @@ -1170,13 +1176,16 @@ enum ImGuiInputTextFlags_ ImGuiInputTextFlags_NoHorizontalScroll = 1 << 15, // Disable following the cursor horizontally ImGuiInputTextFlags_NoUndoRedo = 1 << 16, // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). + // Elide display / Alignment + ImGuiInputTextFlags_ElideLeft = 1 << 17, // When text doesn't fit, elide left side to ensure right side stays visible. Useful for path/filenames. Single-line only! + // Callback features - ImGuiInputTextFlags_CallbackCompletion = 1 << 17, // Callback on pressing TAB (for completion handling) - ImGuiInputTextFlags_CallbackHistory = 1 << 18, // Callback on pressing Up/Down arrows (for history handling) - ImGuiInputTextFlags_CallbackAlways = 1 << 19, // Callback on each iteration. User code may query cursor position, modify text buffer. - ImGuiInputTextFlags_CallbackCharFilter = 1 << 20, // Callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, or return 1 in callback to discard. - ImGuiInputTextFlags_CallbackResize = 1 << 21, // Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. Notify when the string wants to be resized (for string types which hold a cache of their Size). You will be provided a new BufSize in the callback and NEED to honor it. (see misc/cpp/imgui_stdlib.h for an example of using this) - ImGuiInputTextFlags_CallbackEdit = 1 << 22, // Callback on any edit (note that InputText() already returns true on edit, the callback is useful mainly to manipulate the underlying buffer while focus is active) + ImGuiInputTextFlags_CallbackCompletion = 1 << 18, // Callback on pressing TAB (for completion handling) + ImGuiInputTextFlags_CallbackHistory = 1 << 19, // Callback on pressing Up/Down arrows (for history handling) + ImGuiInputTextFlags_CallbackAlways = 1 << 20, // Callback on each iteration. User code may query cursor position, modify text buffer. + ImGuiInputTextFlags_CallbackCharFilter = 1 << 21, // Callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, or return 1 in callback to discard. + ImGuiInputTextFlags_CallbackResize = 1 << 22, // Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. Notify when the string wants to be resized (for string types which hold a cache of their Size). You will be provided a new BufSize in the callback and NEED to honor it. (see misc/cpp/imgui_stdlib.h for an example of using this) + ImGuiInputTextFlags_CallbackEdit = 1 << 23, // Callback on any edit. Note that InputText() already returns true on edit + you can always use IsItemEdited(). The callback is useful to manipulate the underlying buffer while focus is active. // Obsolete names //ImGuiInputTextFlags_AlwaysInsertMode = ImGuiInputTextFlags_AlwaysOverwrite // [renamed in 1.82] name was not matching behavior @@ -1199,14 +1208,16 @@ enum ImGuiTreeNodeFlags_ ImGuiTreeNodeFlags_FramePadding = 1 << 10, // Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget height. Equivalent to calling AlignTextToFramePadding() before the node. ImGuiTreeNodeFlags_SpanAvailWidth = 1 << 11, // Extend hit box to the right-most edge, even if not framed. This is not the default in order to allow adding other items on the same line without using AllowOverlap mode. ImGuiTreeNodeFlags_SpanFullWidth = 1 << 12, // Extend hit box to the left-most and right-most edges (cover the indent area). - ImGuiTreeNodeFlags_SpanTextWidth = 1 << 13, // Narrow hit box + narrow hovering highlight, will only cover the label text. - ImGuiTreeNodeFlags_SpanAllColumns = 1 << 14, // Frame will span all columns of its container table (text will still fit in current column) - ImGuiTreeNodeFlags_NavLeftJumpsBackHere = 1 << 15, // (WIP) Nav: left direction may move to this TreeNode() from any of its child (items submitted between TreeNode and TreePop) + ImGuiTreeNodeFlags_SpanLabelWidth = 1 << 13, // Narrow hit box + narrow hovering highlight, will only cover the label text. + ImGuiTreeNodeFlags_SpanAllColumns = 1 << 14, // Frame will span all columns of its container table (label will still fit in current column) + ImGuiTreeNodeFlags_LabelSpanAllColumns = 1 << 15, // Label will span all columns of its container table //ImGuiTreeNodeFlags_NoScrollOnOpen = 1 << 16, // FIXME: TODO: Disable automatic scroll on TreePop() if node got just open and contents is not visible + ImGuiTreeNodeFlags_NavLeftJumpsBackHere = 1 << 17, // (WIP) Nav: left direction may move to this TreeNode() from any of its child (items submitted between TreeNode and TreePop) ImGuiTreeNodeFlags_CollapsingHeader = ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_NoAutoOpenOnLog, #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImGuiTreeNodeFlags_AllowItemOverlap = ImGuiTreeNodeFlags_AllowOverlap, // Renamed in 1.89.7 + ImGuiTreeNodeFlags_SpanTextWidth = ImGuiTreeNodeFlags_SpanLabelWidth,// Renamed in 1.90.7 #endif }; @@ -1393,6 +1404,7 @@ enum ImGuiDataType_ ImGuiDataType_Float, // float ImGuiDataType_Double, // double ImGuiDataType_Bool, // bool (provided for user convenience, not supported by scalar widgets) + ImGuiDataType_String, // char* (provided for user convenience, not supported by scalar widgets) ImGuiDataType_COUNT }; @@ -1415,21 +1427,18 @@ enum ImGuiSortDirection : ImU8 ImGuiSortDirection_Descending = 2 // Descending = 9->0, Z->A etc. }; -// Since 1.90, defining IMGUI_DISABLE_OBSOLETE_FUNCTIONS automatically defines IMGUI_DISABLE_OBSOLETE_KEYIO as well. -#if defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && !defined(IMGUI_DISABLE_OBSOLETE_KEYIO) -#define IMGUI_DISABLE_OBSOLETE_KEYIO -#endif - // A key identifier (ImGuiKey_XXX or ImGuiMod_XXX value): can represent Keyboard, Mouse and Gamepad values. -// All our named keys are >= 512. Keys value 0 to 511 are left unused as legacy native/opaque key values (< 1.87). -// Since >= 1.89 we increased typing (went from int to enum), some legacy code may need a cast to ImGuiKey. -// Read details about the 1.87 and 1.89 transition : https://github.com/ocornut/imgui/issues/4921 +// All our named keys are >= 512. Keys value 0 to 511 are left unused and were legacy native/opaque key values (< 1.87). +// Support for legacy keys was completely removed in 1.91.5. +// Read details about the 1.87+ transition : https://github.com/ocornut/imgui/issues/4921 // Note that "Keys" related to physical keys and are not the same concept as input "Characters", the later are submitted via io.AddInputCharacter(). // The keyboard key enum values are named after the keys on a standard US keyboard, and on other keyboard types the keys reported may not match the keycaps. enum ImGuiKey : int { // Keyboard ImGuiKey_None = 0, + ImGuiKey_NamedKey_BEGIN = 512, // First valid key value (other than 0) + ImGuiKey_Tab = 512, // == ImGuiKey_NamedKey_BEGIN ImGuiKey_LeftArrow, ImGuiKey_RightArrow, @@ -1517,7 +1526,7 @@ enum ImGuiKey : int // [Internal] Reserved for mod storage ImGuiKey_ReservedForModCtrl, ImGuiKey_ReservedForModShift, ImGuiKey_ReservedForModAlt, ImGuiKey_ReservedForModSuper, - ImGuiKey_COUNT, + ImGuiKey_NamedKey_END, // Keyboard Modifiers (explicitly submitted by backend via AddKeyEvent() calls) // - This is mirroring the data also written to io.KeyCtrl, io.KeyShift, io.KeyAlt, io.KeySuper, in a format allowing @@ -1535,21 +1544,13 @@ enum ImGuiKey : int ImGuiMod_Super = 1 << 15, // Windows/Super (non-macOS), Ctrl (macOS) ImGuiMod_Mask_ = 0xF000, // 4-bits - // [Internal] Prior to 1.87 we required user to fill io.KeysDown[512] using their own native index + the io.KeyMap[] array. - // We are ditching this method but keeping a legacy path for user code doing e.g. IsKeyPressed(MY_NATIVE_KEY_CODE) - // If you need to iterate all keys (for e.g. an input mapper) you may use ImGuiKey_NamedKey_BEGIN..ImGuiKey_NamedKey_END. - ImGuiKey_NamedKey_BEGIN = 512, - ImGuiKey_NamedKey_END = ImGuiKey_COUNT, + // [Internal] If you need to iterate all keys (for e.g. an input mapper) you may use ImGuiKey_NamedKey_BEGIN..ImGuiKey_NamedKey_END. ImGuiKey_NamedKey_COUNT = ImGuiKey_NamedKey_END - ImGuiKey_NamedKey_BEGIN, -#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO - ImGuiKey_KeysData_SIZE = ImGuiKey_NamedKey_COUNT, // Size of KeysData[]: only hold named keys - ImGuiKey_KeysData_OFFSET = ImGuiKey_NamedKey_BEGIN, // Accesses to io.KeysData[] must use (key - ImGuiKey_KeysData_OFFSET) index. -#else - ImGuiKey_KeysData_SIZE = ImGuiKey_COUNT, // Size of KeysData[]: hold legacy 0..512 keycodes + named keys - ImGuiKey_KeysData_OFFSET = 0, // Accesses to io.KeysData[] must use (key - ImGuiKey_KeysData_OFFSET) index. -#endif + //ImGuiKey_KeysData_SIZE = ImGuiKey_NamedKey_COUNT, // Size of KeysData[]: only hold named keys + //ImGuiKey_KeysData_OFFSET = ImGuiKey_NamedKey_BEGIN, // Accesses to io.KeysData[] must use (key - ImGuiKey_NamedKey_BEGIN) index. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiKey_COUNT = ImGuiKey_NamedKey_END, // Obsoleted in 1.91.5 because it was extremely misleading (since named keys don't start at 0 anymore) ImGuiMod_Shortcut = ImGuiMod_Ctrl, // Removed in 1.90.7, you can now simply use ImGuiMod_Ctrl ImGuiKey_ModCtrl = ImGuiMod_Ctrl, ImGuiKey_ModShift = ImGuiMod_Shift, ImGuiKey_ModAlt = ImGuiMod_Alt, ImGuiKey_ModSuper = ImGuiMod_Super, // Renamed in 1.89 //ImGuiKey_KeyPadEnter = ImGuiKey_KeypadEnter, // Renamed in 1.87 @@ -1581,18 +1582,6 @@ enum ImGuiInputFlags_ ImGuiInputFlags_Tooltip = 1 << 18, // Automatically display a tooltip when hovering item [BETA] Unsure of right api (opt-in/opt-out) }; -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO -// OBSOLETED in 1.88 (from July 2022): ImGuiNavInput and io.NavInputs[]. -// Official backends between 1.60 and 1.86: will keep working and feed gamepad inputs as long as IMGUI_DISABLE_OBSOLETE_KEYIO is not set. -// Custom backends: feed gamepad inputs via io.AddKeyEvent() and ImGuiKey_GamepadXXX enums. -enum ImGuiNavInput -{ - ImGuiNavInput_Activate, ImGuiNavInput_Cancel, ImGuiNavInput_Input, ImGuiNavInput_Menu, ImGuiNavInput_DpadLeft, ImGuiNavInput_DpadRight, ImGuiNavInput_DpadUp, ImGuiNavInput_DpadDown, - ImGuiNavInput_LStickLeft, ImGuiNavInput_LStickRight, ImGuiNavInput_LStickUp, ImGuiNavInput_LStickDown, ImGuiNavInput_FocusPrev, ImGuiNavInput_FocusNext, ImGuiNavInput_TweakSlow, ImGuiNavInput_TweakFast, - ImGuiNavInput_COUNT, -}; -#endif - // Configuration flags stored in io.ConfigFlags. Set by user/application. enum ImGuiConfigFlags_ { @@ -1764,12 +1753,19 @@ enum ImGuiColorEditFlags_ ImGuiColorEditFlags_NoSidePreview = 1 << 8, // // ColorPicker: disable bigger color preview on right side of the picker, use small color square preview instead. ImGuiColorEditFlags_NoDragDrop = 1 << 9, // // ColorEdit: disable drag and drop target. ColorButton: disable drag and drop source. ImGuiColorEditFlags_NoBorder = 1 << 10, // // ColorButton: disable border (which is enforced by default) - ImGuiColorEditFlags_Round = 1 << 11, // // ColorButton: remove rounding limit on color picker + + // Alpha preview + // - Prior to 1.91.8 (2025/01/21): alpha was made opaque in the preview by default using old name ImGuiColorEditFlags_AlphaPreview. + // - We now display the preview as transparent by default. You can use ImGuiColorEditFlags_AlphaOpaque to use old behavior. + // - The new flags may be combined better and allow finer controls. + ImGuiColorEditFlags_AlphaOpaque = 1 << 11, // // ColorEdit, ColorPicker, ColorButton: disable alpha in the preview,. Contrary to _NoAlpha it may still be edited when calling ColorEdit4()/ColorPicker4(). For ColorButton() this does the same as _NoAlpha. + ImGuiColorEditFlags_AlphaNoBg = 1 << 12, // // ColorEdit, ColorPicker, ColorButton: disable rendering a checkerboard background behind transparent color. + ImGuiColorEditFlags_AlphaPreviewHalf= 1 << 13, // // ColorEdit, ColorPicker, ColorButton: display half opaque / half transparent preview. // User Options (right-click on widget to change some of them). ImGuiColorEditFlags_AlphaBar = 1 << 16, // // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker. - ImGuiColorEditFlags_AlphaPreview = 1 << 17, // // ColorEdit, ColorPicker, ColorButton: display preview as a transparent color over a checkerboard, instead of opaque. - ImGuiColorEditFlags_AlphaPreviewHalf= 1 << 18, // // ColorEdit, ColorPicker, ColorButton: display half opaque / half checkerboard, instead of opaque. + ImGuiColorEditFlags_Round = 1 << 17, // // ColorEdit, ColorPicker: rouund. + ImGuiColorEditFlags_LargeAlphaGrid = 1 << 18, // // ColorEdit, ColorPicker: large. ImGuiColorEditFlags_HDR = 1 << 19, // // (WIP) ColorEdit: Currently only disable 0.0f..1.0f limits in RGBA edition (note: you probably want to use ImGuiColorEditFlags_Float flag as well). ImGuiColorEditFlags_DisplayRGB = 1 << 20, // [Display] // ColorEdit: override _display_ type among RGB/HSV/Hex. ColorPicker: select any combination using one or more of RGB/HSV/Hex. ImGuiColorEditFlags_DisplayHSV = 1 << 21, // [Display] // " @@ -1786,12 +1782,16 @@ enum ImGuiColorEditFlags_ ImGuiColorEditFlags_DefaultOptions_ = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_PickerHueBar, // [Internal] Masks + ImGuiColorEditFlags_AlphaMask_ = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaOpaque | ImGuiColorEditFlags_AlphaNoBg | ImGuiColorEditFlags_AlphaPreviewHalf, ImGuiColorEditFlags_DisplayMask_ = ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_DisplayHex, ImGuiColorEditFlags_DataTypeMask_ = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_Float, ImGuiColorEditFlags_PickerMask_ = ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_PickerHueBar, ImGuiColorEditFlags_InputMask_ = ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_InputHSV, // Obsolete names +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiColorEditFlags_AlphaPreview = 0, // [Removed in 1.91.8] This is the default now. Will display a checkerboard unless ImGuiColorEditFlags_AlphaNoBg is set. +#endif //ImGuiColorEditFlags_RGB = ImGuiColorEditFlags_DisplayRGB, ImGuiColorEditFlags_HSV = ImGuiColorEditFlags_DisplayHSV, ImGuiColorEditFlags_HEX = ImGuiColorEditFlags_DisplayHex // [renamed in 1.69] }; @@ -1807,6 +1807,7 @@ enum ImGuiSliderFlags_ ImGuiSliderFlags_WrapAround = 1 << 8, // Enable wrapping around from max to min and from min to max. Only supported by DragXXX() functions for now. ImGuiSliderFlags_ClampOnInput = 1 << 9, // Clamp value to min/max bounds when input manually with CTRL+Click. By default CTRL+Click allows going out of bounds. ImGuiSliderFlags_ClampZeroRange = 1 << 10, // Clamp even if min==max==0.0f. Otherwise due to legacy reason DragXXX functions don't clamp with those values. When your clamping limits are dynamic you almost always want to use it. + ImGuiSliderFlags_NoSpeedTweaks = 1 << 11, // Disable keyboard modifiers altering tweak speed. Useful if you want to alter tweak speed yourself based on your own logic. ImGuiSliderFlags_AlwaysClamp = ImGuiSliderFlags_ClampOnInput | ImGuiSliderFlags_ClampZeroRange, ImGuiSliderFlags_InvalidMask_ = 0x7000000F, // [Internal] We treat using those bits as being potentially a 'float power' argument from the previous API that has got miscast to this enum, and will trigger an assert if needed. }; @@ -2243,13 +2244,13 @@ struct ImGuiIO // Font system ImFontAtlas*Fonts; // // Font atlas: load, rasterize and pack one or more fonts into a single texture. float FontGlobalScale; // = 1.0f // Global scale all fonts - bool FontAllowUserScaling; // = false // Allow user scaling text of individual window with CTRL+Wheel. + bool FontAllowUserScaling; // = false // [OBSOLETE] Allow user scaling text of individual window with CTRL+Wheel. ImFont* FontDefault; // = NULL // Font to use on NewFrame(). Use NULL to uses Fonts->Fonts[0]. ImVec2 DisplayFramebufferScale; // = (1, 1) // For retina display or other situations where window coordinates are different from framebuffer coordinates. This generally ends up in ImDrawData::FramebufferScale. // Keyboard/Gamepad Navigation options bool ConfigNavSwapGamepadButtons; // = false // Swap Activate<>Cancel (A<>B) buttons, matching typical "Nintendo/Japanese style" gamepad layout. - bool ConfigNavMoveSetMousePos; // = false // Directional/tabbing navigation teleports the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is difficult. Will update io.MousePos and set io.WantSetMousePos=true. + bool ConfigNavMoveSetMousePos; // = false // Directional/tabbing navigation teleports the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is difficult. Will update io.MousePos and set io.WantSetMousePos=true. bool ConfigNavCaptureKeyboard; // = true // Sets io.WantCaptureKeyboard when io.NavActive is set. bool ConfigNavEscapeClearFocusItem; // = true // Pressing Escape can clear focused item + navigation id/highlight. Set to false if you want to always keep highlight on. bool ConfigNavEscapeClearFocusWindow;// = false // Pressing Escape can clear focused window as well (super set of io.ConfigNavEscapeClearFocusItem). @@ -2264,8 +2265,9 @@ struct ImGuiIO bool ConfigInputTextCursorBlink; // = true // Enable blinking cursor (optional as some users consider it to be distracting). bool ConfigInputTextEnterKeepActive; // = false // [BETA] Pressing Enter will keep item active and select contents (single-line only). bool ConfigDragClickToInputText; // = false // [BETA] Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving). Not desirable on devices without a keyboard. - bool ConfigWindowsResizeFromEdges; // = true // Enable resizing of windows from their edges and from the lower-left corner. This requires (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback. (This used to be a per-window ImGuiWindowFlags_ResizeFromAnySide flag) + bool ConfigWindowsResizeFromEdges; // = true // Enable resizing of windows from their edges and from the lower-left corner. This requires ImGuiBackendFlags_HasMouseCursors for better mouse cursor feedback. (This used to be a per-window ImGuiWindowFlags_ResizeFromAnySide flag) bool ConfigWindowsMoveFromTitleBarOnly; // = false // Enable allowing to move windows only when clicking on their title bar. Does not apply to windows without a title bar. + bool ConfigWindowsCopyContentsWithCtrlC; // = false // [EXPERIMENTAL] CTRL+C copy the contents of focused window into the clipboard. Experimental because: (1) has known issues with nested Begin/End pairs (2) text output quality varies (3) text output is in submission order rather than spatial order. bool ConfigScrollbarScrollByPage; // = true // Enable scrolling page by page when clicking outside the scrollbar grab. When disabled, always scroll to clicked location. When enabled, Shift+Click scrolls to clicked location. float ConfigMemoryCompactTimer; // = 60.0f // Timer (in seconds) to free transient windows/tables memory buffers when unused. Set to -1.0f to disable. @@ -2332,6 +2334,7 @@ struct ImGuiIO // (the imgui_impl_xxxx backend files are setting those up for you) //------------------------------------------------------------------ + // Nowadays those would be stored in ImGuiPlatformIO but we are leaving them here for legacy reasons. // Optional: Platform/Renderer backend name (informational only! will be displayed in About Window) + User data for backend/wrappers to store their own stuff. const char* BackendPlatformName; // = NULL const char* BackendRendererName; // = NULL @@ -2405,7 +2408,7 @@ struct ImGuiIO // Other state maintained from data above + IO function calls ImGuiKeyChord KeyMods; // Key mods flags (any of ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Alt/ImGuiMod_Super flags, same as io.KeyCtrl/KeyShift/KeyAlt/KeySuper but merged into flags. Read-only, updated by NewFrame() - ImGuiKeyData KeysData[ImGuiKey_KeysData_SIZE]; // Key state for all known keys. Use IsKeyXXX() functions to access this. + ImGuiKeyData KeysData[ImGuiKey_NamedKey_COUNT];// Key state for all known keys. Use IsKeyXXX() functions to access this. bool WantCaptureMouseUnlessPopupClose; // Alternative to WantCaptureMouse: (WantCaptureMouse == true && WantCaptureMouseUnlessPopupClose == false) when a click over void is expected to close a popup. ImVec2 MousePosPrev; // Previous mouse position (note that MouseDelta is not necessary == MousePos-MousePosPrev, in case either position is invalid) ImVec2 MouseClickedPos[5]; // Position at time of clicking @@ -2415,6 +2418,7 @@ struct ImGuiIO ImU16 MouseClickedCount[5]; // == 0 (not clicked), == 1 (same as MouseClicked[]), == 2 (double-clicked), == 3 (triple-clicked) etc. when going from !Down to Down ImU16 MouseClickedLastCount[5]; // Count successive number of clicks. Stays valid after mouse release. Reset after another click is done. bool MouseReleased[5]; // Mouse button went from Down to !Down + double MouseReleasedTime[5]; // Time of last released (rarely used! but useful to handle delayed single-click when trying to disambiguate them from double-click). bool MouseDownOwned[5]; // Track if button was clicked inside a dear imgui window or over void blocked by a popup. We don't request mouse capture from the application if click started outside ImGui bounds. bool MouseDownOwnedUnlessPopupClose[5]; // Track if button was clicked inside a dear imgui window. bool MouseWheelRequestAxisSwap; // On a non-Mac system, holding SHIFT requests WheelY to perform the equivalent of a WheelX event. On a Mac system this is already enforced by the system. @@ -2425,20 +2429,18 @@ struct ImGuiIO float PenPressure; // Touch/Pen pressure (0.0f to 1.0f, should be >0.0f only when MouseDown[0] == true). Helper storage currently unused by Dear ImGui. bool AppFocusLost; // Only modify via AddFocusEvent() bool AppAcceptingEvents; // Only modify via SetAppAcceptingEvents() - ImS8 BackendUsingLegacyKeyArrays; // -1: unknown, 0: using AddKeyEvent(), 1: using legacy io.KeysDown[] - bool BackendUsingLegacyNavInputArray; // 0: using AddKeyAnalogEvent(), 1: writing to legacy io.NavInputs[] directly ImWchar16 InputQueueSurrogate; // For AddInputCharacterUTF16() ImVector InputQueueCharacters; // Queue of _characters_ input (obtained by platform backend). Fill using AddInputCharacter() helper. // Legacy: before 1.87, we required backend to fill io.KeyMap[] (imgui->native map) during initialization and io.KeysDown[] (native indices) every frame. // This is still temporarily supported as a legacy feature. However the new preferred scheme is for backend to call io.AddKeyEvent(). // Old (<1.87): ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_Space]) --> New (1.87+) ImGui::IsKeyPressed(ImGuiKey_Space) -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - int KeyMap[ImGuiKey_COUNT]; // [LEGACY] Input: map of indices into the KeysDown[512] entries array which represent your "native" keyboard state. The first 512 are now unused and should be kept zero. Legacy backend will write into KeyMap[] using ImGuiKey_ indices which are always >512. - bool KeysDown[ImGuiKey_COUNT]; // [LEGACY] Input: Keyboard keys that are pressed (ideally left in the "native" order your engine has access to keyboard keys, so you can use your own defines/enums for keys). This used to be [512] sized. It is now ImGuiKey_COUNT to allow legacy io.KeysDown[GetKeyIndex(...)] to work without an overflow. - float NavInputs[ImGuiNavInput_COUNT]; // [LEGACY] Since 1.88, NavInputs[] was removed. Backends from 1.60 to 1.86 won't build. Feed gamepad inputs via io.AddKeyEvent() and ImGuiKey_GamepadXXX enums. + // Old (<1.87): ImGui::IsKeyPressed(MYPLATFORM_KEY_SPACE) --> New (1.87+) ImGui::IsKeyPressed(ImGuiKey_Space) + // Read https://github.com/ocornut/imgui/issues/4921 for details. + //int KeyMap[ImGuiKey_COUNT]; // [LEGACY] Input: map of indices into the KeysDown[512] entries array which represent your "native" keyboard state. The first 512 are now unused and should be kept zero. Legacy backend will write into KeyMap[] using ImGuiKey_ indices which are always >512. + //bool KeysDown[ImGuiKey_COUNT]; // [LEGACY] Input: Keyboard keys that are pressed (ideally left in the "native" order your engine has access to keyboard keys, so you can use your own defines/enums for keys). This used to be [512] sized. It is now ImGuiKey_COUNT to allow legacy io.KeysDown[GetKeyIndex(...)] to work without an overflow. + //float NavInputs[ImGuiNavInput_COUNT]; // [LEGACY] Since 1.88, NavInputs[] was removed. Backends from 1.60 to 1.86 won't build. Feed gamepad inputs via io.AddKeyEvent() and ImGuiKey_GamepadXXX enums. //void* ImeWindowHandle; // [Obsoleted in 1.87] Set ImGuiViewport::PlatformHandleRaw instead. Set this to your HWND to get automatic IME cursor positioning. -#endif // Legacy: before 1.91.1, clipboard functions were stored in ImGuiIO instead of ImGuiPlatformIO. // As this is will affect all users of custom engines/backends, we are providing proper legacy redirection (will obsolete). @@ -2458,7 +2460,7 @@ struct ImGuiIO // Shared state of InputText(), passed as an argument to your callback when a ImGuiInputTextFlags_Callback* flag is used. // The callback function should return 0 by default. // Callbacks (follow a flag name and see comments in ImGuiInputTextFlags_ declarations for more details) -// - ImGuiInputTextFlags_CallbackEdit: Callback on buffer edit (note that InputText() already returns true on edit, the callback is useful mainly to manipulate the underlying buffer while focus is active) +// - ImGuiInputTextFlags_CallbackEdit: Callback on buffer edit. Note that InputText() already returns true on edit + you can always use IsItemEdited(). The callback is useful to manipulate the underlying buffer while focus is active. // - ImGuiInputTextFlags_CallbackAlways: Callback on each iteration // - ImGuiInputTextFlags_CallbackCompletion: Callback on pressing TAB // - ImGuiInputTextFlags_CallbackHistory: Callback on pressing Up/Down arrows @@ -2711,6 +2713,7 @@ struct ImGuiListClipper // - It is important that we are keeping those disabled by default so they don't leak in user space. // - This is in order to allow user enabling implicit cast operators between ImVec2/ImVec4 and their own types (using IM_VEC2_CLASS_EXTRA in imconfig.h) // - Add '#define IMGUI_DEFINE_MATH_OPERATORS' before including this file (or in imconfig.h) to access courtesy maths operators for ImVec2 and ImVec4. +// - We intentionally provide ImVec2*float but not float*ImVec2: this is rare enough and we want to reduce the surface for possible user mistake. #ifdef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED IM_MSVC_RUNTIME_CHECKS_OFF @@ -2738,7 +2741,8 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE #endif // Helpers macros to generate 32-bit encoded colors -// User can declare their own format by #defining the 5 _SHIFT/_MASK macros in their imconfig file. +// - User can declare their own format by #defining the 5 _SHIFT/_MASK macros in their imconfig file. +// - Any setting other than the default will need custom backend support. The only standard backend that supports anything else than the default is DirectX9. #ifndef IM_COL32_R_SHIFT #ifdef IMGUI_USE_BGRA_PACKED_COLOR #define IM_COL32_R_SHIFT 16 @@ -2911,7 +2915,7 @@ struct ImGuiSelectionBasicStorage IMGUI_API void Clear(); // Clear selection IMGUI_API void Swap(ImGuiSelectionBasicStorage& r); // Swap two selections IMGUI_API void SetItemSelected(ImGuiID id, bool selected); // Add/remove an item from selection (generally done by ApplyRequests() function) - IMGUI_API bool GetNextSelectedItem(void** opaque_it, ImGuiID* out_id); // Iterate selection with 'void* it = NULL; ImGuiId id; while (selection.GetNextSelectedItem(&it, &id)) { ... }' + IMGUI_API bool GetNextSelectedItem(void** opaque_it, ImGuiID* out_id); // Iterate selection with 'void* it = NULL; ImGuiID id; while (selection.GetNextSelectedItem(&it, &id)) { ... }' inline ImGuiID GetStorageIdFromIndex(int idx) { return AdapterIndexToStorageId(this, idx); } // Convert index to item id based on provided adapter. }; @@ -2935,7 +2939,7 @@ struct ImGuiSelectionExternalStorage // The maximum line width to bake anti-aliased textures for. Build atlas with ImFontAtlasFlags_NoBakedLines to disable baking. #ifndef IM_DRAWLIST_TEX_LINES_WIDTH_MAX -#define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (63) +#define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (32) #endif // ImDrawCallback: Draw callbacks for advanced uses [configurable type: override in imconfig.h] @@ -3009,7 +3013,6 @@ struct ImDrawChannel ImVector _IdxBuffer; }; - // Split/Merge functions are used to split the draw list into different layers which can be drawn into out of order. // This is used by the Columns/Tables API, so items of each column can be batched together in a same draw call. struct ImDrawListSplitter @@ -3065,7 +3068,7 @@ enum ImDrawListFlags_ // access the current window draw list and draw custom primitives. // You can interleave normal ImGui:: calls and adding primitives to the current draw list. // In single viewport mode, top-left is == GetMainViewport()->Pos (generally 0,0), bottom-right is == GetMainViewport()->Pos+Size (generally io.DisplaySize). -// You are totally free to apply whatever transformation matrix to want to the data (depending on the use of the transformation you may want to apply it to ClipRect as well!) +// You are totally free to apply whatever transformation matrix you want to the data (depending on the use of the transformation you may want to apply it to ClipRect as well!) // Important: Primitives are always added to the list and not culled (culling is done at higher-level by ImGui:: functions), if you use this API a lot consider coarse culling your drawn objects. struct ImDrawList { @@ -3089,10 +3092,11 @@ struct ImDrawList float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content const char* _OwnerName; // Pointer to owner window's name for debugging - // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData() or create and use your own ImDrawListSharedData (so you can use ImDrawList without ImGui) - ImDrawList(ImDrawListSharedData* shared_data) { memset(this, 0, sizeof(*this)); _Data = shared_data; } + // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData(). + // (advanced: you may create and use your own ImDrawListSharedData so you can use ImDrawList without ImGui, but that's more involved) + IMGUI_API ImDrawList(ImDrawListSharedData* shared_data); + IMGUI_API ~ImDrawList(); - ~ImDrawList() { _ClearFreeMemory(); } IMGUI_API void PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect = false); // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) IMGUI_API void PushClipRectFullScreen(); IMGUI_API void PopClipRect(); @@ -3123,7 +3127,7 @@ struct ImDrawList IMGUI_API void AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f); IMGUI_API void AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot = 0.0f, int num_segments = 0); IMGUI_API void AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL); - IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); + IMGUI_API void AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); IMGUI_API void AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0); // Cubic Bezier (4 control points) IMGUI_API void AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments = 0); // Quadratic Bezier (3 control points) @@ -3243,26 +3247,27 @@ struct ImDrawData // [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontAtlasFlags, ImFontAtlas, ImFontGlyphRangesBuilder, ImFont) //----------------------------------------------------------------------------- +// A font input/source (we may rename this to ImFontSource in the future) struct ImFontConfig { void* FontData; // // TTF/OTF data int FontDataSize; // // TTF/OTF data size bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the container ImFontAtlas (will delete memory itself). + bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights. + bool PixelSnapH; // false // Align every glyph AdvanceX to pixel boundaries. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1. int FontNo; // 0 // Index of font within TTF/OTF file + int OversampleH; // 0 (2) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1 or 2 depending on size. Note the difference between 2 and 3 is minimal. You can reduce this to 1 for large glyphs save memory. Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details. + int OversampleV; // 0 (1) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1. This is not really useful as we don't use sub-pixel positions on the Y axis. float SizePixels; // // Size in pixels for rasterizer (more or less maps to the resulting font height). - int OversampleH; // 2 // Rasterize at higher quality for sub-pixel positioning. Note the difference between 2 and 3 is minimal. You can reduce this to 1 for large glyphs save memory. Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details. - int OversampleV; // 1 // Rasterize at higher quality for sub-pixel positioning. This is not really useful as we don't use sub-pixel positions on the Y axis. - bool PixelSnapH; // false // Align every glyph to pixel boundary. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1. - ImVec2 GlyphExtraSpacing; // 0, 0 // Extra spacing (in pixels) between glyphs. Only X axis is supported for now. + ImVec2 GlyphExtraSpacing; // 0, 0 // Extra spacing (in pixels) between glyphs when rendered: essentially add to glyph->AdvanceX. Only X axis is supported for now. ImVec2 GlyphOffset; // 0, 0 // Offset all glyphs from this font input. const ImWchar* GlyphRanges; // NULL // THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font float GlyphMaxAdvanceX; // FLT_MAX // Maximum AdvanceX for glyphs - bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights. unsigned int FontBuilderFlags; // 0 // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure. float RasterizerMultiply; // 1.0f // Linearly brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. This is a silly thing we may remove in the future. float RasterizerDensity; // 1.0f // DPI scale for rasterization, not altering other font metrics: make it easy to swap between e.g. a 100% and a 400% fonts for a zooming display. IMPORTANT: If you increase this it is expected that you increase font scale accordingly, otherwise quality may look lowered. - ImWchar EllipsisChar; // -1 // Explicitly specify unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used. + ImWchar EllipsisChar; // 0 // Explicitly specify Unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used. // [Internal] char Name[40]; // Name (strictly to ease debugging) @@ -3302,13 +3307,16 @@ struct ImFontGlyphRangesBuilder // See ImFontAtlas::AddCustomRectXXX functions. struct ImFontAtlasCustomRect { - unsigned short Width, Height; // Input // Desired rectangle dimension unsigned short X, Y; // Output // Packed position in Atlas - unsigned int GlyphID; // Input // For custom font glyphs only (ID < 0x110000) + + // [Internal] + unsigned short Width, Height; // Input // Desired rectangle dimension + unsigned int GlyphID : 31; // Input // For custom font glyphs only (ID < 0x110000) + unsigned int GlyphColored : 1; // Input // For custom font glyphs only: glyph is colored, removed tinting. float GlyphAdvanceX; // Input // For custom font glyphs only: glyph xadvance ImVec2 GlyphOffset; // Input // For custom font glyphs only: glyph display offset ImFont* Font; // Input // For custom font glyphs only: target font - ImFontAtlasCustomRect() { Width = Height = 0; X = Y = 0xFFFF; GlyphID = 0; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0, 0); Font = NULL; } + ImFontAtlasCustomRect() { X = Y = 0xFFFF; Width = Height = 0; GlyphID = 0; GlyphColored = 0; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0, 0); Font = NULL; } bool IsPacked() const { return X != 0xFFFF; } }; @@ -3349,8 +3357,8 @@ struct ImFontAtlas IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_data_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp. IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter. IMGUI_API void ClearInputData(); // Clear input data (all ImFontConfig structures including sizes, TTF data, glyph ranges, etc.) = all the data used to build the texture and fonts. + IMGUI_API void ClearFonts(); // Clear input+output font data (same as ClearInputData() + glyphs storage, UV coordinates). IMGUI_API void ClearTexData(); // Clear output texture data (CPU side). Saves RAM once the texture has been copied to graphics memory. - IMGUI_API void ClearFonts(); // Clear output font data (glyphs storage, UV coordinates). IMGUI_API void Clear(); // Clear all input and output. // Build atlas, retrieve pixel data. @@ -3383,7 +3391,7 @@ struct ImFontAtlas IMGUI_API const ImWchar* GetGlyphRangesVietnamese(); // Default + Vietnamese characters //------------------------------------------- - // [BETA] Custom Rectangles/Glyphs API + // [ALPHA] Custom Rectangles/Glyphs API //------------------------------------------- // You can request arbitrary rectangles to be packed into the atlas, for your own purposes. @@ -3408,12 +3416,12 @@ struct ImFontAtlas ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) ImTextureID TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. - int TexGlyphPadding; // Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0 (will also need to set AntiAliasedLinesUseTex = false). - bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. + int TexGlyphPadding; // FIXME: Should be called "TexPackPadding". Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0 (will also need to set AntiAliasedLinesUseTex = false). void* UserData; // Store your own atlas related user-data (if e.g. you have multiple font atlas). // [Internal] // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you. + bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. bool TexReady; // Set when texture was built matching current font input bool TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format. unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight @@ -3444,46 +3452,47 @@ struct ImFontAtlas // ImFontAtlas automatically loads a default embedded font for you when you call GetTexDataAsAlpha8() or GetTexDataAsRGBA32(). struct ImFont { - // Members: Hot ~20/24 bytes (for CalcTextSize) - ImVector IndexAdvanceX; // 12-16 // out // // Sparse. Glyphs->AdvanceX in a directly indexable way (cache-friendly for CalcTextSize functions which only this info, and are often bottleneck in large UI). + // [Internal] Members: Hot ~20/24 bytes (for CalcTextSize) + ImVector IndexAdvanceX; // 12-16 // out // Sparse. Glyphs->AdvanceX in a directly indexable way (cache-friendly for CalcTextSize functions which only this info, and are often bottleneck in large UI). float FallbackAdvanceX; // 4 // out // = FallbackGlyph->AdvanceX - float FontSize; // 4 // in // // Height of characters/line, set during loading (don't change after loading) + float FontSize; // 4 // in // Height of characters/line, set during loading (don't change after loading) - // Members: Hot ~28/40 bytes (for CalcTextSize + render loop) - ImVector IndexLookup; // 12-16 // out // // Sparse. Index glyphs by Unicode code-point. - ImVector Glyphs; // 12-16 // out // // All glyphs. + // [Internal] Members: Hot ~28/40 bytes (for RenderText loop) + ImVector IndexLookup; // 12-16 // out // Sparse. Index glyphs by Unicode code-point. + ImVector Glyphs; // 12-16 // out // All glyphs. const ImFontGlyph* FallbackGlyph; // 4-8 // out // = FindGlyph(FontFallbackChar) - // Members: Cold ~32/40 bytes - ImFontAtlas* ContainerAtlas; // 4-8 // out // // What we has been loaded into - const ImFontConfig* ConfigData; // 4-8 // in // // Pointer within ContainerAtlas->ConfigData - short ConfigDataCount; // 2 // in // ~ 1 // Number of ImFontConfig involved in creating this font. Bigger than 1 when merging multiple font sources into one ImFont. - ImWchar FallbackChar; // 2 // out // = FFFD/'?' // Character used if a glyph isn't found. - ImWchar EllipsisChar; // 2 // out // = '...'/'.'// Character used for ellipsis rendering. + // [Internal] Members: Cold ~32/40 bytes + // Conceptually ConfigData[] is the list of font sources merged to create this font. + ImFontAtlas* ContainerAtlas; // 4-8 // out // What we has been loaded into + const ImFontConfig* ConfigData; // 4-8 // in // Pointer within ContainerAtlas->ConfigData to ConfigDataCount instances + short ConfigDataCount; // 2 // in // Number of ImFontConfig involved in creating this font. Usually 1, or >1 when merging multiple font sources into one ImFont. short EllipsisCharCount; // 1 // out // 1 or 3 - float EllipsisWidth; // 4 // out // Width - float EllipsisCharStep; // 4 // out // Step between characters when EllipsisCount > 0 + ImWchar EllipsisChar; // 2-4 // out // Character used for ellipsis rendering ('...'). + ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?') + float EllipsisWidth; // 4 // out // Total ellipsis Width + float EllipsisCharStep; // 4 // out // Step between characters when EllipsisCount > 0 + float Scale; // 4 // in // Base font scale (1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() + float Ascent, Descent; // 4+4 // out // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] (unscaled) + int MetricsTotalSurface;// 4 // out // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) bool DirtyLookupTables; // 1 // out // - float Scale; // 4 // in // = 1.f // Base font scale, multiplied by the per-window font scale which you can adjust with SetWindowFontScale() - float Ascent, Descent; // 4+4 // out // // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] (unscaled) - int MetricsTotalSurface;// 4 // out // // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) - ImU8 Used4kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/4096/8]; // 2 bytes if ImWchar=ImWchar16, 34 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. + ImU8 Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 16 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. // Methods IMGUI_API ImFont(); IMGUI_API ~ImFont(); - IMGUI_API const ImFontGlyph*FindGlyph(ImWchar c) const; - IMGUI_API const ImFontGlyph*FindGlyphNoFallback(ImWchar c) const; - float GetCharAdvance(ImWchar c) const { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; } - bool IsLoaded() const { return ContainerAtlas != NULL; } - const char* GetDebugName() const { return ConfigData ? ConfigData->Name : ""; } + IMGUI_API const ImFontGlyph*FindGlyph(ImWchar c); + IMGUI_API const ImFontGlyph*FindGlyphNoFallback(ImWchar c); + float GetCharAdvance(ImWchar c) { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; } + bool IsLoaded() const { return ContainerAtlas != NULL; } + const char* GetDebugName() const { return ConfigData ? ConfigData->Name : ""; } // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. - IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8 - IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; - IMGUI_API void RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c) const; - IMGUI_API void RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false) const; + IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL); // utf8 + IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width); + IMGUI_API void RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c); + IMGUI_API void RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false); // [Internal] Don't use! IMGUI_API void BuildLookupTable(); @@ -3617,9 +3626,6 @@ namespace ImGui // OBSOLETED in 1.89.4 (from March 2023) static inline void PushAllowKeyboardFocus(bool tab_stop) { PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); } static inline void PopAllowKeyboardFocus() { PopItemFlag(); } - // OBSOLETED in 1.87 (from February 2022 but more formally obsoleted April 2024) - IMGUI_API ImGuiKey GetKeyIndex(ImGuiKey key); // Map ImGuiKey_* values into legacy native key index. == io.KeyMap[key]. When using a 1.87+ backend using io.AddKeyEvent(), calling GetKeyIndex() with ANY ImGuiKey_XXXX values will return the same value! - //static inline ImGuiKey GetKeyIndex(ImGuiKey key) { IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END); return key; } // Some of the older obsolete names along with their replacement (commented out so they are not reported in IDE) //-- OBSOLETED in 1.89 (from August 2022) @@ -3627,6 +3633,9 @@ namespace ImGui //-- OBSOLETED in 1.88 (from May 2022) //static inline void CaptureKeyboardFromApp(bool want_capture_keyboard = true) { SetNextFrameWantCaptureKeyboard(want_capture_keyboard); } // Renamed as name was misleading + removed default value. //static inline void CaptureMouseFromApp(bool want_capture_mouse = true) { SetNextFrameWantCaptureMouse(want_capture_mouse); } // Renamed as name was misleading + removed default value. + //-- OBSOLETED in 1.87 (from February 2022, more formally obsoleted April 2024) + //IMGUI_API ImGuiKey GetKeyIndex(ImGuiKey key); { IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END); const ImGuiKeyData* key_data = GetKeyData(key); return (ImGuiKey)(key_data - g.IO.KeysData); } // Map ImGuiKey_* values into legacy native key index. == io.KeyMap[key]. When using a 1.87+ backend using io.AddKeyEvent(), calling GetKeyIndex() with ANY ImGuiKey_XXXX values will return the same value! + //static inline ImGuiKey GetKeyIndex(ImGuiKey key) { IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END); return key; } //-- OBSOLETED in 1.86 (from November 2021) //IMGUI_API void CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end); // Code removed, see 1.90 for last version of the code. Calculate range of visible items for large list of evenly sized items. Prefer using ImGuiListClipper. //-- OBSOLETED in 1.85 (from August 2021) diff --git a/Amalgam/include/ImGui/imgui_draw.cpp b/Amalgam/include/ImGui/imgui_draw.cpp index 4de5a352..6309fb6e 100644 --- a/Amalgam/include/ImGui/imgui_draw.cpp +++ b/Amalgam/include/ImGui/imgui_draw.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.91.4 +// dear imgui, v1.91.8 // (drawing and font code) /* @@ -14,7 +14,7 @@ Index of this file: // [SECTION] Helpers ShadeVertsXXX functions // [SECTION] ImFontConfig // [SECTION] ImFontAtlas -// [SECTION] ImFontAtlas glyph ranges helpers +// [SECTION] ImFontAtlas: glyph ranges helpers // [SECTION] ImFontGlyphRangesBuilder // [SECTION] ImFont // [SECTION] ImGui Internal Render Helpers @@ -66,13 +66,18 @@ Index of this file: #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wreserved-identifier" // warning: identifier '_Xxx' is reserved because it starts with '_' followed by a capital letter #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access +#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type +#pragma clang diagnostic ignored "-Wcast-qual" // warning: cast from 'const xxxx *' to 'xxx *' drops const qualifier #elif defined(__GNUC__) -#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind -#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used -#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function -#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value -#pragma GCC diagnostic ignored "-Wstack-protector" // warning: stack protector not protecting local variables: variable length buffer -#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wstack-protector" // warning: stack protector not protecting local variables: variable length buffer +#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when simplifying division / ..when changing X +- C1 cmp C2 to X cmp C2 -+ C1 +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers #endif //------------------------------------------------------------------------- @@ -101,16 +106,15 @@ namespace IMGUI_STB_NAMESPACE #if defined(__clang__) #pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-function" +#pragma clang diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used #pragma clang diagnostic ignored "-Wmissing-prototypes" #pragma clang diagnostic ignored "-Wimplicit-fallthrough" -#pragma clang diagnostic ignored "-Wcast-qual" // warning: cast from 'const xxxx *' to 'xxx *' drops const qualifier #endif #if defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" // warning: comparison is always true due to limited range of data type [-Wtype-limits] -#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" // warning: this statement may fall through #endif #ifndef STB_RECT_PACK_IMPLEMENTATION // in case the user already have an implementation in the _same_ compilation unit (e.g. unity builds) @@ -217,7 +221,7 @@ void ImGui::StyleColorsDark(ImGuiStyle* dst) colors[ImGuiCol_TabSelectedOverline] = colors[ImGuiCol_HeaderActive]; colors[ImGuiCol_TabDimmed] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); colors[ImGuiCol_TabDimmedSelected] = ImLerp(colors[ImGuiCol_TabSelected], colors[ImGuiCol_TitleBg], 0.40f); - colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.50f, 0.50f, 0.50f, 0.00f); colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); @@ -280,7 +284,7 @@ void ImGui::StyleColorsClassic(ImGuiStyle* dst) colors[ImGuiCol_TabSelectedOverline] = colors[ImGuiCol_HeaderActive]; colors[ImGuiCol_TabDimmed] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); colors[ImGuiCol_TabDimmedSelected] = ImLerp(colors[ImGuiCol_TabSelected], colors[ImGuiCol_TitleBg], 0.40f); - colors[ImGuiCol_TabDimmedSelectedOverline] = colors[ImGuiCol_HeaderActive]; + colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.53f, 0.53f, 0.87f, 0.00f); colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); @@ -344,7 +348,7 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) colors[ImGuiCol_TabSelectedOverline] = colors[ImGuiCol_HeaderActive]; colors[ImGuiCol_TabDimmed] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); colors[ImGuiCol_TabDimmedSelected] = ImLerp(colors[ImGuiCol_TabSelected], colors[ImGuiCol_TitleBg], 0.40f); - colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.26f, 0.59f, 1.00f, 1.00f); + colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.26f, 0.59f, 1.00f, 0.00f); colors[ImGuiCol_PlotLines] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f); colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); @@ -393,6 +397,17 @@ void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError); } +ImDrawList::ImDrawList(ImDrawListSharedData* shared_data) +{ + memset(this, 0, sizeof(*this)); + _Data = shared_data; +} + +ImDrawList::~ImDrawList() +{ + _ClearFreeMemory(); +} + // Initialize before use in a new frame. We always have a command ready in the buffer. // In the majority of cases, you would want to call PushClipRect() and PushTextureID() after this. void ImDrawList::_ResetForNewFrame() @@ -1646,7 +1661,7 @@ void ImDrawList::AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const Im PathStroke(col, 0, thickness); } -void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) +void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) { if ((col & IM_COL32_A_MASK) == 0) return; @@ -1654,8 +1669,7 @@ void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, // Accept null ranges if (text_begin == text_end || text_begin[0] == 0) return; - if (text_end == NULL) - text_end = text_begin + strlen(text_begin); + // No need to strlen() here: font->RenderText() will do it and may early out. // Pull default font/size from the shared ImDrawListSharedData instance if (font == NULL) @@ -1678,7 +1692,7 @@ void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, void ImDrawList::AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end) { - AddText(NULL, 0.0f, pos, col, text_begin, text_end); + AddText(_Data->Font, _Data->FontSize, pos, col, text_begin, text_end); } void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col) @@ -2360,17 +2374,49 @@ ImFontConfig::ImFontConfig() { memset(this, 0, sizeof(*this)); FontDataOwnedByAtlas = true; - OversampleH = 2; - OversampleV = 1; + OversampleH = 0; // Auto == 1 or 2 depending on size + OversampleV = 0; // Auto == 1 GlyphMaxAdvanceX = FLT_MAX; RasterizerMultiply = 1.0f; RasterizerDensity = 1.0f; - EllipsisChar = (ImWchar)-1; + EllipsisChar = 0; } //----------------------------------------------------------------------------- // [SECTION] ImFontAtlas //----------------------------------------------------------------------------- +// - Default texture data encoded in ASCII +// - ImFontAtlas::ClearInputData() +// - ImFontAtlas::ClearTexData() +// - ImFontAtlas::ClearFonts() +// - ImFontAtlas::Clear() +// - ImFontAtlas::GetTexDataAsAlpha8() +// - ImFontAtlas::GetTexDataAsRGBA32() +// - ImFontAtlas::AddFont() +// - ImFontAtlas::AddFontDefault() +// - ImFontAtlas::AddFontFromFileTTF() +// - ImFontAtlas::AddFontFromMemoryTTF() +// - ImFontAtlas::AddFontFromMemoryCompressedTTF() +// - ImFontAtlas::AddFontFromMemoryCompressedBase85TTF() +// - ImFontAtlas::AddCustomRectRegular() +// - ImFontAtlas::AddCustomRectFontGlyph() +// - ImFontAtlas::CalcCustomRectUV() +// - ImFontAtlas::GetMouseCursorTexData() +// - ImFontAtlas::Build() +// - ImFontAtlasBuildMultiplyCalcLookupTable() +// - ImFontAtlasBuildMultiplyRectAlpha8() +// - ImFontAtlasBuildWithStbTruetype() +// - ImFontAtlasGetBuilderForStbTruetype() +// - ImFontAtlasUpdateConfigDataPointers() +// - ImFontAtlasBuildSetupFont() +// - ImFontAtlasBuildPackCustomRects() +// - ImFontAtlasBuildRender8bppRectFromString() +// - ImFontAtlasBuildRender32bppRectFromString() +// - ImFontAtlasBuildRenderDefaultTexData() +// - ImFontAtlasBuildRenderLinesTexData() +// - ImFontAtlasBuildInit() +// - ImFontAtlasBuildFinish() +//----------------------------------------------------------------------------- // A work of art lies ahead! (. = white layer, X = black layer, others are blank) // The 2x2 white texels on the top left are the ones we'll use everywhere in Dear ImGui to render filled shapes. @@ -2474,6 +2520,7 @@ void ImFontAtlas::ClearTexData() void ImFontAtlas::ClearFonts() { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + ClearInputData(); Fonts.clear_delete(); TexReady = false; } @@ -2526,7 +2573,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); IM_ASSERT(font_cfg->FontData != NULL && font_cfg->FontDataSize > 0); IM_ASSERT(font_cfg->SizePixels > 0.0f && "Is ImFontConfig struct correctly initialized?"); - IM_ASSERT(font_cfg->OversampleH > 0 && font_cfg->OversampleV > 0 && "Is ImFontConfig struct correctly initialized?"); + IM_ASSERT(font_cfg->RasterizerDensity > 0.0f && "Is ImFontConfig struct correctly initialized?"); // Create new font if (!font_cfg->MergeMode) @@ -2545,9 +2592,13 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) memcpy(new_font_cfg.FontData, font_cfg->FontData, (size_t)new_font_cfg.FontDataSize); } - if (new_font_cfg.DstFont->EllipsisChar == (ImWchar)-1) - new_font_cfg.DstFont->EllipsisChar = font_cfg->EllipsisChar; + // Round font size + // - We started rounding in 1.90 WIP (18991) as our layout system currently doesn't support non-rounded font size well yet. + // - Note that using io.FontGlobalScale or SetWindowFontScale(), with are legacy-ish, partially supported features, can still lead to unrounded sizes. + // - We may support it better later and remove this rounding. + new_font_cfg.SizePixels = ImTrunc(new_font_cfg.SizePixels); + // Pointers to ConfigData and BuilderData are otherwise dangling ImFontAtlasUpdateConfigDataPointers(this); // Invalidate texture @@ -2559,7 +2610,6 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) // Default font TTF is compressed with stb_compress then base85 encoded (see misc/fonts/binary_to_compressed_c.cpp for encoder) static unsigned int stb_decompress_length(const unsigned char* input); static unsigned int stb_decompress(unsigned char* output, const unsigned char* input, unsigned int length); -static const char* GetDefaultCompressedFontDataTTFBase85(); static unsigned int Decode85Byte(char c) { return c >= '\\' ? c-36 : c-35; } static void Decode85(const unsigned char* src, unsigned char* dst) { @@ -2571,10 +2621,14 @@ static void Decode85(const unsigned char* src, unsigned char* dst) dst += 4; } } +#ifndef IMGUI_DISABLE_DEFAULT_FONT +static const char* GetDefaultCompressedFontDataTTF(int* out_size); +#endif // Load embedded ProggyClean.ttf at size 13, disable oversampling ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) { +#ifndef IMGUI_DISABLE_DEFAULT_FONT ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); if (!font_cfg_template) { @@ -2588,10 +2642,16 @@ ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) font_cfg.EllipsisChar = (ImWchar)0x0085; font_cfg.GlyphOffset.y = 1.0f * IM_TRUNC(font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units - const char* ttf_compressed_base85 = GetDefaultCompressedFontDataTTFBase85(); + int ttf_compressed_size = 0; + const char* ttf_compressed = GetDefaultCompressedFontDataTTF(&ttf_compressed_size); const ImWchar* glyph_ranges = font_cfg.GlyphRanges != NULL ? font_cfg.GlyphRanges : GetGlyphRangesDefault(); - ImFont* font = AddFontFromMemoryCompressedBase85TTF(ttf_compressed_base85, font_cfg.SizePixels, &font_cfg, glyph_ranges); + ImFont* font = AddFontFromMemoryCompressedTTF(ttf_compressed, ttf_compressed_size, font_cfg.SizePixels, &font_cfg, glyph_ranges); return font; +#else + IM_ASSERT(0 && "AddFontDefault() disabled in this build."); + IM_UNUSED(font_cfg_template); + return NULL; +#endif // #ifndef IMGUI_DISABLE_DEFAULT_FONT } ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) @@ -2675,6 +2735,7 @@ int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int r.Width = (unsigned short)width; r.Height = (unsigned short)height; r.GlyphID = id; + r.GlyphColored = 0; // Set to 1 manually to mark glyph as colored // FIXME: No official API for that (#8133) r.GlyphAdvanceX = advance_x; r.GlyphOffset = offset; r.Font = font; @@ -2758,6 +2819,13 @@ void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsig *data = table[*data]; } +void ImFontAtlasBuildGetOversampleFactors(const ImFontConfig* cfg, int* out_oversample_h, int* out_oversample_v) +{ + // Automatically disable horizontal oversampling over size 36 + *out_oversample_h = (cfg->OversampleH != 0) ? cfg->OversampleH : (cfg->SizePixels * cfg->RasterizerDensity > 36.0f || cfg->PixelSnapH) ? 1 : 2; + *out_oversample_v = (cfg->OversampleV != 0) ? cfg->OversampleV : 1; +} + #ifdef IMGUI_ENABLE_STB_TRUETYPE // Temporary data for one source font (multiple source fonts can be merged into one destination ImFont) // (C++03 doesn't allow instancing ImVector<> with function-local types so we declare the type here.) @@ -2911,6 +2979,7 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) int total_surface = 0; int buf_rects_out_n = 0; int buf_packedchars_out_n = 0; + const int pack_padding = atlas->TexGlyphPadding; for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) { ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; @@ -2922,30 +2991,35 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) buf_rects_out_n += src_tmp.GlyphsCount; buf_packedchars_out_n += src_tmp.GlyphsCount; - // Convert our ranges in the format stb_truetype wants + // Automatic selection of oversampling parameters ImFontConfig& cfg = atlas->ConfigData[src_i]; + int oversample_h, oversample_v; + ImFontAtlasBuildGetOversampleFactors(&cfg, &oversample_h, &oversample_v); + + // Convert our ranges in the format stb_truetype wants src_tmp.PackRange.font_size = cfg.SizePixels * cfg.RasterizerDensity; src_tmp.PackRange.first_unicode_codepoint_in_range = 0; src_tmp.PackRange.array_of_unicode_codepoints = src_tmp.GlyphsList.Data; src_tmp.PackRange.num_chars = src_tmp.GlyphsList.Size; src_tmp.PackRange.chardata_for_range = src_tmp.PackedChars; - src_tmp.PackRange.h_oversample = (unsigned char)cfg.OversampleH; - src_tmp.PackRange.v_oversample = (unsigned char)cfg.OversampleV; + src_tmp.PackRange.h_oversample = (unsigned char)oversample_h; + src_tmp.PackRange.v_oversample = (unsigned char)oversample_v; // Gather the sizes of all rectangles we will need to pack (this loop is based on stbtt_PackFontRangesGatherRects) const float scale = (cfg.SizePixels > 0.0f) ? stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, cfg.SizePixels * cfg.RasterizerDensity) : stbtt_ScaleForMappingEmToPixels(&src_tmp.FontInfo, -cfg.SizePixels * cfg.RasterizerDensity); - const int padding = atlas->TexGlyphPadding; for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++) { int x0, y0, x1, y1; const int glyph_index_in_font = stbtt_FindGlyphIndex(&src_tmp.FontInfo, src_tmp.GlyphsList[glyph_i]); IM_ASSERT(glyph_index_in_font != 0); - stbtt_GetGlyphBitmapBoxSubpixel(&src_tmp.FontInfo, glyph_index_in_font, scale * cfg.OversampleH, scale * cfg.OversampleV, 0, 0, &x0, &y0, &x1, &y1); - src_tmp.Rects[glyph_i].w = (stbrp_coord)(x1 - x0 + padding + cfg.OversampleH - 1); - src_tmp.Rects[glyph_i].h = (stbrp_coord)(y1 - y0 + padding + cfg.OversampleV - 1); + stbtt_GetGlyphBitmapBoxSubpixel(&src_tmp.FontInfo, glyph_index_in_font, scale * oversample_h, scale * oversample_v, 0, 0, &x0, &y0, &x1, &y1); + src_tmp.Rects[glyph_i].w = (stbrp_coord)(x1 - x0 + pack_padding + oversample_h - 1); + src_tmp.Rects[glyph_i].h = (stbrp_coord)(y1 - y0 + pack_padding + oversample_v - 1); total_surface += src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_i].h; } } + for (int i = 0; i < atlas->CustomRects.Size; i++) + total_surface += (atlas->CustomRects[i].Width + pack_padding) * (atlas->CustomRects[i].Height + pack_padding); // We need a width for the skyline algorithm, any width! // The exact width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. @@ -2961,7 +3035,8 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). const int TEX_HEIGHT_MAX = 1024 * 32; stbtt_pack_context spc = {}; - stbtt_PackBegin(&spc, NULL, atlas->TexWidth, TEX_HEIGHT_MAX, 0, atlas->TexGlyphPadding, NULL); + stbtt_PackBegin(&spc, NULL, atlas->TexWidth, TEX_HEIGHT_MAX, 0, 0, NULL); + spc.padding = atlas->TexGlyphPadding; // Because we mixup stbtt_PackXXX and stbrp_PackXXX there's a bit of a hack here, not passing the value to stbtt_PackBegin() allows us to still pack a TexWidth-1 wide item. (#8107) ImFontAtlasBuildPackCustomRects(atlas, spc.pack_info); // 6. Pack each source font. No rendering yet, we are working with rectangles in an infinitely tall texture at this point. @@ -3107,13 +3182,14 @@ void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opa if (user_rects.Size < 1) { __builtin_unreachable(); } // Workaround for GCC bug if IM_ASSERT() is defined to conditionally throw (see #5343) #endif + const int pack_padding = atlas->TexGlyphPadding; ImVector pack_rects; pack_rects.resize(user_rects.Size); memset(pack_rects.Data, 0, (size_t)pack_rects.size_in_bytes()); for (int i = 0; i < user_rects.Size; i++) { - pack_rects[i].w = user_rects[i].Width; - pack_rects[i].h = user_rects[i].Height; + pack_rects[i].w = user_rects[i].Width + pack_padding; + pack_rects[i].h = user_rects[i].Height + pack_padding; } stbrp_pack_rects(pack_context, &pack_rects[0], pack_rects.Size); for (int i = 0; i < pack_rects.Size; i++) @@ -3121,7 +3197,7 @@ void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opa { user_rects[i].X = (unsigned short)pack_rects[i].x; user_rects[i].Y = (unsigned short)pack_rects[i].y; - IM_ASSERT(pack_rects[i].w == user_rects[i].Width && pack_rects[i].h == user_rects[i].Height); + IM_ASSERT(pack_rects[i].w == user_rects[i].Width + pack_padding && pack_rects[i].h == user_rects[i].Height + pack_padding); atlas->TexHeight = ImMax(atlas->TexHeight, pack_rects[i].y + pack_rects[i].h); } } @@ -3152,9 +3228,23 @@ static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) IM_ASSERT(r->IsPacked()); const int w = atlas->TexWidth; - if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) + if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) { - // Render/copy pixels + // White pixels only + IM_ASSERT(r->Width == 2 && r->Height == 2); + const int offset = (int)r->X + (int)r->Y * w; + if (atlas->TexPixelsAlpha8 != NULL) + { + atlas->TexPixelsAlpha8[offset] = atlas->TexPixelsAlpha8[offset + 1] = atlas->TexPixelsAlpha8[offset + w] = atlas->TexPixelsAlpha8[offset + w + 1] = 0xFF; + } + else + { + atlas->TexPixelsRGBA32[offset] = atlas->TexPixelsRGBA32[offset + 1] = atlas->TexPixelsRGBA32[offset + w] = atlas->TexPixelsRGBA32[offset + w + 1] = IM_COL32_WHITE; + } + } + else + { + // White pixels and mouse cursor IM_ASSERT(r->Width == FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1 && r->Height == FONT_ATLAS_DEFAULT_TEX_DATA_H); const int x_for_white = r->X; const int x_for_black = r->X + FONT_ATLAS_DEFAULT_TEX_DATA_W + 1; @@ -3169,20 +3259,6 @@ static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) ImFontAtlasBuildRender32bppRectFromString(atlas, x_for_black, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X', IM_COL32_WHITE); } } - else - { - // Render 4 white pixels - IM_ASSERT(r->Width == 2 && r->Height == 2); - const int offset = (int)r->X + (int)r->Y * w; - if (atlas->TexPixelsAlpha8 != NULL) - { - atlas->TexPixelsAlpha8[offset] = atlas->TexPixelsAlpha8[offset + 1] = atlas->TexPixelsAlpha8[offset + w] = atlas->TexPixelsAlpha8[offset + w + 1] = 0xFF; - } - else - { - atlas->TexPixelsRGBA32[offset] = atlas->TexPixelsRGBA32[offset + 1] = atlas->TexPixelsRGBA32[offset + w] = atlas->TexPixelsRGBA32[offset + w + 1] = IM_COL32_WHITE; - } - } atlas->TexUvWhitePixel = ImVec2((r->X + 0.5f) * atlas->TexUvScale.x, (r->Y + 0.5f) * atlas->TexUvScale.y); } @@ -3194,38 +3270,38 @@ static void ImFontAtlasBuildRenderLinesTexData(ImFontAtlas* atlas) // This generates a triangular shape in the texture, with the various line widths stacked on top of each other to allow interpolation between them ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdLines); IM_ASSERT(r->IsPacked()); - for (unsigned int n = 0; n < IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row + for (int n = 0; n < IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row { // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle - unsigned int y = n; - unsigned int line_width = n; - unsigned int pad_left = (r->Width - line_width) / 2; - unsigned int pad_right = r->Width - (pad_left + line_width); + int y = n; + int line_width = n; + int pad_left = (r->Width - line_width) / 2; + int pad_right = r->Width - (pad_left + line_width); // Write each slice IM_ASSERT(pad_left + line_width + pad_right == r->Width && y < r->Height); // Make sure we're inside the texture bounds before we start writing pixels if (atlas->TexPixelsAlpha8 != NULL) { unsigned char* write_ptr = &atlas->TexPixelsAlpha8[r->X + ((r->Y + y) * atlas->TexWidth)]; - for (unsigned int i = 0; i < pad_left; i++) + for (int i = 0; i < pad_left; i++) *(write_ptr + i) = 0x00; - for (unsigned int i = 0; i < line_width; i++) + for (int i = 0; i < line_width; i++) *(write_ptr + pad_left + i) = 0xFF; - for (unsigned int i = 0; i < pad_right; i++) + for (int i = 0; i < pad_right; i++) *(write_ptr + pad_left + line_width + i) = 0x00; } else { unsigned int* write_ptr = &atlas->TexPixelsRGBA32[r->X + ((r->Y + y) * atlas->TexWidth)]; - for (unsigned int i = 0; i < pad_left; i++) + for (int i = 0; i < pad_left; i++) *(write_ptr + i) = IM_COL32(255, 255, 255, 0); - for (unsigned int i = 0; i < line_width; i++) + for (int i = 0; i < line_width; i++) *(write_ptr + pad_left + i) = IM_COL32_WHITE; - for (unsigned int i = 0; i < pad_right; i++) + for (int i = 0; i < pad_right; i++) *(write_ptr + pad_left + line_width + i) = IM_COL32(255, 255, 255, 0); } @@ -3240,13 +3316,6 @@ static void ImFontAtlasBuildRenderLinesTexData(ImFontAtlas* atlas) // Note: this is called / shared by both the stb_truetype and the FreeType builder void ImFontAtlasBuildInit(ImFontAtlas* atlas) { - // Round font size - // - We started rounding in 1.90 WIP (18991) as our layout system currently doesn't support non-rounded font size well yet. - // - Note that using io.FontGlobalScale or SetWindowFontScale(), with are legacy-ish, partially supported features, can still lead to unrounded sizes. - // - We may support it better later and remove this rounding. - for (ImFontConfig& cfg : atlas->ConfigData) - cfg.SizePixels = ImTrunc(cfg.SizePixels); - // Register texture region for mouse cursors or standard white pixels if (atlas->PackIdMouseCursors < 0) { @@ -3285,6 +3354,8 @@ void ImFontAtlasBuildFinish(ImFontAtlas* atlas) ImVec2 uv0, uv1; atlas->CalcCustomRectUV(r, &uv0, &uv1); r->Font->AddGlyph(NULL, (ImWchar)r->GlyphID, r->GlyphOffset.x, r->GlyphOffset.y, r->GlyphOffset.x + r->Width, r->GlyphOffset.y + r->Height, uv0.x, uv0.y, uv1.x, uv1.y, r->GlyphAdvanceX); + if (r->GlyphColored) + r->Font->Glyphs.back().Colored = 1; } // Build all fonts lookup tables @@ -3295,6 +3366,20 @@ void ImFontAtlasBuildFinish(ImFontAtlas* atlas) atlas->TexReady = true; } +//------------------------------------------------------------------------- +// [SECTION] ImFontAtlas: glyph ranges helpers +//------------------------------------------------------------------------- +// - GetGlyphRangesDefault() +// - GetGlyphRangesGreek() +// - GetGlyphRangesKorean() +// - GetGlyphRangesChineseFull() +// - GetGlyphRangesChineseSimplifiedCommon() +// - GetGlyphRangesJapanese() +// - GetGlyphRangesCyrillic() +// - GetGlyphRangesThai() +// - GetGlyphRangesVietnamese() +//----------------------------------------------------------------------------- + // Retrieve list of range (2 int per range, values are inclusive) const ImWchar* ImFontAtlas::GetGlyphRangesDefault() { @@ -3356,10 +3441,6 @@ static void UnpackAccumulativeOffsetsIntoRanges(int base_codepoint, const short* out_ranges[0] = 0; } -//------------------------------------------------------------------------- -// [SECTION] ImFontAtlas glyph ranges helpers -//------------------------------------------------------------------------- - const ImWchar* ImFontAtlas::GetGlyphRangesChineseSimplifiedCommon() { // Store 2500 regularly used characters for Simplified Chinese. @@ -3606,8 +3687,8 @@ ImFont::ImFont() { FontSize = 0.0f; FallbackAdvanceX = 0.0f; - FallbackChar = (ImWchar)-1; - EllipsisChar = (ImWchar)-1; + FallbackChar = 0; + EllipsisChar = 0; EllipsisWidth = EllipsisCharStep = 0.0f; EllipsisCharCount = 0; FallbackGlyph = NULL; @@ -3618,7 +3699,7 @@ ImFont::ImFont() Scale = 1.0f; Ascent = Descent = 0.0f; MetricsTotalSurface = 0; - memset(Used4kPagesMap, 0, sizeof(Used4kPagesMap)); + memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); } ImFont::~ImFont() @@ -3638,6 +3719,7 @@ void ImFont::ClearOutputData() DirtyLookupTables = true; Ascent = Descent = 0.0f; MetricsTotalSurface = 0; + memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); } static ImWchar FindFirstExistingGlyph(ImFont* font, const ImWchar* candidate_chars, int candidate_chars_count) @@ -3645,7 +3727,7 @@ static ImWchar FindFirstExistingGlyph(ImFont* font, const ImWchar* candidate_cha for (int n = 0; n < candidate_chars_count; n++) if (font->FindGlyphNoFallback(candidate_chars[n]) != NULL) return candidate_chars[n]; - return (ImWchar)-1; + return 0; } void ImFont::BuildLookupTable() @@ -3660,17 +3742,17 @@ void ImFont::BuildLookupTable() IndexAdvanceX.clear(); IndexLookup.clear(); DirtyLookupTables = false; - memset(Used4kPagesMap, 0, sizeof(Used4kPagesMap)); + memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); GrowIndex(max_codepoint + 1); for (int i = 0; i < Glyphs.Size; i++) { int codepoint = (int)Glyphs[i].Codepoint; IndexAdvanceX[codepoint] = Glyphs[i].AdvanceX; - IndexLookup[codepoint] = (ImWchar)i; + IndexLookup[codepoint] = (ImU16)i; // Mark 4K page as used - const int page_n = codepoint / 4096; - Used4kPagesMap[page_n >> 3] |= 1 << (page_n & 7); + const int page_n = codepoint / 8192; + Used8kPagesMap[page_n >> 3] |= 1 << (page_n & 7); } // Create a glyph to handle TAB @@ -3684,7 +3766,7 @@ void ImFont::BuildLookupTable() tab_glyph.Codepoint = '\t'; tab_glyph.AdvanceX *= IM_TABSIZE; IndexAdvanceX[(int)tab_glyph.Codepoint] = (float)tab_glyph.AdvanceX; - IndexLookup[(int)tab_glyph.Codepoint] = (ImWchar)(Glyphs.Size - 1); + IndexLookup[(int)tab_glyph.Codepoint] = (ImU16)(Glyphs.Size - 1); } // Mark special glyphs as not visible (note that AddGlyph already mark as non-visible glyphs with zero-size polygons) @@ -3712,35 +3794,35 @@ void ImFont::BuildLookupTable() // Setup Ellipsis character. It is required for rendering elided text. We prefer using U+2026 (horizontal ellipsis). // However some old fonts may contain ellipsis at U+0085. Here we auto-detect most suitable ellipsis character. // FIXME: Note that 0x2026 is rarely included in our font ranges. Because of this we are more likely to use three individual dots. - const ImWchar ellipsis_chars[] = { (ImWchar)0x2026, (ImWchar)0x0085 }; + const ImWchar ellipsis_chars[] = { ConfigData->EllipsisChar, (ImWchar)0x2026, (ImWchar)0x0085 }; const ImWchar dots_chars[] = { (ImWchar)'.', (ImWchar)0xFF0E }; - if (EllipsisChar == (ImWchar)-1) + if (EllipsisChar == 0) EllipsisChar = FindFirstExistingGlyph(this, ellipsis_chars, IM_ARRAYSIZE(ellipsis_chars)); const ImWchar dot_char = FindFirstExistingGlyph(this, dots_chars, IM_ARRAYSIZE(dots_chars)); - if (EllipsisChar != (ImWchar)-1) + if (EllipsisChar != 0) { EllipsisCharCount = 1; EllipsisWidth = EllipsisCharStep = FindGlyph(EllipsisChar)->X1; } - else if (dot_char != (ImWchar)-1) + else if (dot_char != 0) { - const ImFontGlyph* glyph = FindGlyph(dot_char); + const ImFontGlyph* dot_glyph = FindGlyph(dot_char); EllipsisChar = dot_char; EllipsisCharCount = 3; - EllipsisCharStep = (glyph->X1 - glyph->X0) + 1.0f; - EllipsisWidth = EllipsisCharStep * 3.0f - 1.0f; + EllipsisCharStep = (float)(int)(dot_glyph->X1 - dot_glyph->X0) + 1.0f; + EllipsisWidth = ImMax(dot_glyph->AdvanceX, dot_glyph->X0 + EllipsisCharStep * 3.0f - 1.0f); // FIXME: Slightly odd for normally mono-space fonts but since this is used for trailing contents. } } -// API is designed this way to avoid exposing the 4K page size +// API is designed this way to avoid exposing the 8K page size // e.g. use with IsGlyphRangeUnused(0, 255) bool ImFont::IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last) { - unsigned int page_begin = (c_begin / 4096); - unsigned int page_last = (c_last / 4096); + unsigned int page_begin = (c_begin / 8192); + unsigned int page_last = (c_last / 8192); for (unsigned int page_n = page_begin; page_n <= page_last; page_n++) - if ((page_n >> 3) < sizeof(Used4kPagesMap)) - if (Used4kPagesMap[page_n >> 3] & (1 << (page_n & 7))) + if ((page_n >> 3) < sizeof(Used8kPagesMap)) + if (Used8kPagesMap[page_n >> 3] & (1 << (page_n & 7))) return false; return true; } @@ -3757,7 +3839,7 @@ void ImFont::GrowIndex(int new_size) if (new_size <= IndexLookup.Size) return; IndexAdvanceX.resize(new_size, -1.0f); - IndexLookup.resize(new_size, (ImWchar)-1); + IndexLookup.resize(new_size, (ImU16)-1); } // x0/y0/x1/y1 are offset from the character upper-left layout position, in pixels. Therefore x0/y0 are often fairly close to zero. @@ -3785,8 +3867,9 @@ void ImFont::AddGlyph(const ImFontConfig* cfg, ImWchar codepoint, float x0, floa advance_x += cfg->GlyphExtraSpacing.x; } + int glyph_idx = Glyphs.Size; Glyphs.resize(Glyphs.Size + 1); - ImFontGlyph& glyph = Glyphs.back(); + ImFontGlyph& glyph = Glyphs[glyph_idx]; glyph.Codepoint = (unsigned int)codepoint; glyph.Visible = (x0 != x1) && (y0 != y1); glyph.Colored = false; @@ -3799,6 +3882,7 @@ void ImFont::AddGlyph(const ImFontConfig* cfg, ImWchar codepoint, float x0, floa glyph.U1 = u1; glyph.V1 = v1; glyph.AdvanceX = advance_x; + IM_ASSERT(Glyphs.Size < 0xFFFF); // IndexLookup[] hold 16-bit values and -1 is reserved. // Compute rough surface usage metrics (+1 to account for average padding, +0.99 to round) // We use (U1-U0)*TexWidth instead of X1-X0 to account for oversampling. @@ -3812,37 +3896,38 @@ void ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst) IM_ASSERT(IndexLookup.Size > 0); // Currently this can only be called AFTER the font has been built, aka after calling ImFontAtlas::GetTexDataAs*() function. unsigned int index_size = (unsigned int)IndexLookup.Size; - if (dst < index_size && IndexLookup.Data[dst] == (ImWchar)-1 && !overwrite_dst) // 'dst' already exists + if (dst < index_size && IndexLookup.Data[dst] == (ImU16)-1 && !overwrite_dst) // 'dst' already exists return; if (src >= index_size && dst >= index_size) // both 'dst' and 'src' don't exist -> no-op return; GrowIndex(dst + 1); - IndexLookup[dst] = (src < index_size) ? IndexLookup.Data[src] : (ImWchar)-1; + IndexLookup[dst] = (src < index_size) ? IndexLookup.Data[src] : (ImU16)-1; IndexAdvanceX[dst] = (src < index_size) ? IndexAdvanceX.Data[src] : 1.0f; } -const ImFontGlyph* ImFont::FindGlyph(ImWchar c) const +// Find glyph, return fallback if missing +const ImFontGlyph* ImFont::FindGlyph(ImWchar c) { if (c >= (size_t)IndexLookup.Size) return FallbackGlyph; - const ImWchar i = IndexLookup.Data[c]; - if (i == (ImWchar)-1) + const ImU16 i = IndexLookup.Data[c]; + if (i == (ImU16)-1) return FallbackGlyph; return &Glyphs.Data[i]; } -const ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) const +const ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) { if (c >= (size_t)IndexLookup.Size) return NULL; - const ImWchar i = IndexLookup.Data[c]; - if (i == (ImWchar)-1) + const ImU16 i = IndexLookup.Data[c]; + if (i == (ImU16)-1) return NULL; return &Glyphs.Data[i]; } -// Wrapping skips upcoming blanks +// Trim trailing space and find beginning of next line static inline const char* CalcWordWrapNextLineStartA(const char* text, const char* text_end) { while (text < text_end && ImCharIsBlankA(*text)) @@ -3852,10 +3937,12 @@ static inline const char* CalcWordWrapNextLineStartA(const char* text, const cha return text; } +#define ImFontGetCharAdvanceX(_FONT, _CH) ((int)(_CH) < (_FONT)->IndexAdvanceX.Size ? (_FONT)->IndexAdvanceX.Data[_CH] : (_FONT)->FallbackAdvanceX) + // Simple word-wrapping for English, not full-featured. Please submit failing cases! // This will return the next location to wrap from. If no wrapping if necessary, this will fast-forward to e.g. text_end. // FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.) -const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const +const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) { // For references, possible wrap point marked with ^ // "aaa bbb, ccc,ddd. eee fff. ggg!" @@ -3904,7 +3991,7 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c } } - const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX.Data[c] : FallbackAdvanceX); + const float char_width = ImFontGetCharAdvanceX(this, c); if (ImCharIsBlankW(c)) { if (inside_word) @@ -3953,7 +4040,7 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c return s; } -ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining) const +ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining) { if (!text_end) text_end = text_begin + strlen(text_begin); // FIXME-OPT: Need to avoid this. @@ -4009,7 +4096,7 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons continue; } - const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX.Data[c] : FallbackAdvanceX) * scale; + const float char_width = ImFontGetCharAdvanceX(this, c) * scale; if (line_width + char_width >= max_width) { s = prev_s; @@ -4032,7 +4119,7 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons } // Note: as with every ImDrawList drawing function, this expects that the font atlas texture is bound. -void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c) const +void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c) { const ImFontGlyph* glyph = FindGlyph(c); if (!glyph || !glyph->Visible) @@ -4047,20 +4134,20 @@ void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, Im } // Note: as with every ImDrawList drawing function, this expects that the font atlas texture is bound. -void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) const +void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) { - if (!text_end) - text_end = text_begin + strlen(text_begin); // ImGui:: functions generally already provides a valid text_end, so this is merely to handle direct calls. - // Align to be pixel perfect float x = IM_TRUNC(pos.x); float y = IM_TRUNC(pos.y); if (y > clip_rect.w) return; - const float start_x = x; + if (!text_end) + text_end = text_begin + strlen(text_begin); // ImGui:: functions generally already provides a valid text_end, so this is merely to handle direct calls. + const float scale = size / FontSize; const float line_height = FontSize * scale; + const float origin_x = x; const bool word_wrap_enabled = (wrap_width > 0.0f); // Fast-forward to first visible line @@ -4119,11 +4206,11 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im { // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. if (!word_wrap_eol) - word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - start_x)); + word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - origin_x)); if (s >= word_wrap_eol) { - x = start_x; + x = origin_x; y += line_height; if (y > clip_rect.w) break; // break out of main loop @@ -4144,7 +4231,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im { if (c == '\n') { - x = start_x; + x = origin_x; y += line_height; if (y > clip_rect.w) break; // break out of main loop @@ -4424,6 +4511,8 @@ void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x); if (x2 <= x1) continue; + + /* ImDrawFlags cell_flags = ImDrawFlags_RoundCornersNone; if (y1 <= p_min.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundCornersTopLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundCornersTopRight; } if (y2 >= p_max.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundCornersBottomLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundCornersBottomRight; } @@ -4431,6 +4520,11 @@ void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p // Combine flags cell_flags = (flags == ImDrawFlags_RoundCornersNone || cell_flags == ImDrawFlags_RoundCornersNone) ? ImDrawFlags_RoundCornersNone : (cell_flags & flags); draw_list->AddRectFilled(ImVec2(x1, y1), ImVec2(x2, y2), col_bg2, rounding, cell_flags); + */ + + draw_list->PushClipRect(ImVec2(x1, y1), ImVec2(x2, y2), true); + draw_list->AddRectFilled(p_min, p_max, col_bg2, rounding, flags); + draw_list->PopClipRect(); } } } @@ -4567,101 +4661,187 @@ static unsigned int stb_decompress(unsigned char *output, const unsigned char *i // MIT license (see License.txt in http://www.proggyfonts.net/index.php?menu=download) // Download and more information at http://www.proggyfonts.net or http://upperboundsinteractive.com/fonts.php //----------------------------------------------------------------------------- -// File: 'ProggyClean.ttf' (41208 bytes) -// Exported using misc/fonts/binary_to_compressed_c.cpp (with compression + base85 string encoding). -// The purpose of encoding as base85 instead of "0x00,0x01,..." style is only save on _source code_ size. -//----------------------------------------------------------------------------- -static const char proggy_clean_ttf_compressed_data_base85[11980 + 1] = - "7])#######hV0qs'/###[),##/l:$#Q6>##5[n42>c-TH`->>#/e>11NNV=Bv(*:.F?uu#(gRU.o0XGH`$vhLG1hxt9?W`#,5LsCp#-i>.r$<$6pD>Lb';9Crc6tgXmKVeU2cD4Eo3R/" - "2*>]b(MC;$jPfY.;h^`IWM9Qo#t'X#(v#Y9w0#1D$CIf;W'#pWUPXOuxXuU(H9M(1=Ke$$'5F%)]0^#0X@U.a$FBjVQTSDgEKnIS7EM9>ZY9w0#L;>>#Mx&4Mvt//L[MkA#W@lK.N'[0#7RL_&#w+F%HtG9M#XL`N&.,GM4Pg;--VsM.M0rJfLH2eTM`*oJMHRC`N" - "kfimM2J,W-jXS:)r0wK#@Fge$U>`w'N7G#$#fB#$E^$#:9:hk+eOe--6x)F7*E%?76%^GMHePW-Z5l'&GiF#$956:rS?dA#fiK:)Yr+`�j@'DbG&#^$PG.Ll+DNa&VZ>1i%h1S9u5o@YaaW$e+bROPOpxTO7Stwi1::iB1q)C_=dV26J;2,]7op$]uQr@_V7$q^%lQwtuHY]=DX,n3L#0PHDO4f9>dC@O>HBuKPpP*E,N+b3L#lpR/MrTEH.IAQk.a>D[.e;mc." - "x]Ip.PH^'/aqUO/$1WxLoW0[iLAw=4h(9.`G" - "CRUxHPeR`5Mjol(dUWxZa(>STrPkrJiWx`5U7F#.g*jrohGg`cg:lSTvEY/EV_7H4Q9[Z%cnv;JQYZ5q.l7Zeas:HOIZOB?Ggv:[7MI2k).'2($5FNP&EQ(,)" - "U]W]+fh18.vsai00);D3@4ku5P?DP8aJt+;qUM]=+b'8@;mViBKx0DE[-auGl8:PJ&Dj+M6OC]O^((##]`0i)drT;-7X`=-H3[igUnPG-NZlo.#k@h#=Ork$m>a>$-?Tm$UV(?#P6YY#" - "'/###xe7q.73rI3*pP/$1>s9)W,JrM7SN]'/4C#v$U`0#V.[0>xQsH$fEmPMgY2u7Kh(G%siIfLSoS+MK2eTM$=5,M8p`A.;_R%#u[K#$x4AG8.kK/HSB==-'Ie/QTtG?-.*^N-4B/ZM" - "_3YlQC7(p7q)&](`6_c)$/*JL(L-^(]$wIM`dPtOdGA,U3:w2M-0+WomX2u7lqM2iEumMTcsF?-aT=Z-97UEnXglEn1K-bnEO`gu" - "Ft(c%=;Am_Qs@jLooI&NX;]0#j4#F14;gl8-GQpgwhrq8'=l_f-b49'UOqkLu7-##oDY2L(te+Mch&gLYtJ,MEtJfLh'x'M=$CS-ZZ%P]8bZ>#S?YY#%Q&q'3^Fw&?D)UDNrocM3A76/" - "/oL?#h7gl85[qW/NDOk%16ij;+:1a'iNIdb-ou8.P*w,v5#EI$TWS>Pot-R*H'-SEpA:g)f+O$%%`kA#G=8RMmG1&O`>to8bC]T&$,n.LoO>29sp3dt-52U%VM#q7'DHpg+#Z9%H[Ket`e;)f#Km8&+DC$I46>#Kr]]u-[=99tts1.qb#q72g1WJO81q+eN'03'eM>&1XxY-caEnO" - "j%2n8)),?ILR5^.Ibn<-X-Mq7[a82Lq:F&#ce+S9wsCK*x`569E8ew'He]h:sI[2LM$[guka3ZRd6:t%IG:;$%YiJ:Nq=?eAw;/:nnDq0(CYcMpG)qLN4$##&J-XTt,%OVU4)S1+R-#dg0/Nn?Ku1^0f$B*P:Rowwm-`0PKjYDDM'3]d39VZHEl4,.j']Pk-M.h^&:0FACm$maq-&sgw0t7/6(^xtk%" - "LuH88Fj-ekm>GA#_>568x6(OFRl-IZp`&b,_P'$MhLbxfc$mj`,O;&%W2m`Zh:/)Uetw:aJ%]K9h:TcF]u_-Sj9,VK3M.*'&0D[Ca]J9gp8,kAW]" - "%(?A%R$f<->Zts'^kn=-^@c4%-pY6qI%J%1IGxfLU9CP8cbPlXv);C=b),<2mOvP8up,UVf3839acAWAW-W?#ao/^#%KYo8fRULNd2.>%m]UK:n%r$'sw]J;5pAoO_#2mO3n,'=H5(et" - "Hg*`+RLgv>=4U8guD$I%D:W>-r5V*%j*W:Kvej.Lp$'?;++O'>()jLR-^u68PHm8ZFWe+ej8h:9r6L*0//c&iH&R8pRbA#Kjm%upV1g:" - "a_#Ur7FuA#(tRh#.Y5K+@?3<-8m0$PEn;J:rh6?I6uG<-`wMU'ircp0LaE_OtlMb&1#6T.#FDKu#1Lw%u%+GM+X'e?YLfjM[VO0MbuFp7;>Q&#WIo)0@F%q7c#4XAXN-U&VBpqB>0ie&jhZ[?iLR@@_AvA-iQC(=ksRZRVp7`.=+NpBC%rh&3]R:8XDmE5^V8O(x<-+k?'(^](H.aREZSi,#1:[IXaZFOm<-ui#qUq2$##Ri;u75OK#(RtaW-K-F`S+cF]uN`-KMQ%rP/Xri.LRcB##=YL3BgM/3M" - "D?@f&1'BW-)Ju#bmmWCMkk&#TR`C,5d>g)F;t,4:@_l8G/5h4vUd%&%950:VXD'QdWoY-F$BtUwmfe$YqL'8(PWX(" - "P?^@Po3$##`MSs?DWBZ/S>+4%>fX,VWv/w'KD`LP5IbH;rTV>n3cEK8U#bX]l-/V+^lj3;vlMb&[5YQ8#pekX9JP3XUC72L,,?+Ni&co7ApnO*5NK,((W-i:$,kp'UDAO(G0Sq7MVjJs" - "bIu)'Z,*[>br5fX^:FPAWr-m2KgLQ_nN6'8uTGT5g)uLv:873UpTLgH+#FgpH'_o1780Ph8KmxQJ8#H72L4@768@Tm&Q" - "h4CB/5OvmA&,Q&QbUoi$a_%3M01H)4x7I^&KQVgtFnV+;[Pc>[m4k//,]1?#`VY[Jr*3&&slRfLiVZJ:]?=K3Sw=[$=uRB?3xk48@aege0jT6'N#(q%.O=?2S]u*(m<-" - "V8J'(1)G][68hW$5'q[GC&5j`TE?m'esFGNRM)j,ffZ?-qx8;->g4t*:CIP/[Qap7/9'#(1sao7w-.qNUdkJ)tCF&#B^;xGvn2r9FEPFFFcL@.iFNkTve$m%#QvQS8U@)2Z+3K:AKM5i" - "sZ88+dKQ)W6>J%CL`.d*(B`-n8D9oK-XV1q['-5k'cAZ69e;D_?$ZPP&s^+7])$*$#@QYi9,5P r+$%CE=68>K8r0=dSC%%(@p7" - ".m7jilQ02'0-VWAgTlGW'b)Tq7VT9q^*^$$.:&N@@" - "$&)WHtPm*5_rO0&e%K&#-30j(E4#'Zb.o/(Tpm$>K'f@[PvFl,hfINTNU6u'0pao7%XUp9]5.>%h`8_=VYbxuel.NTSsJfLacFu3B'lQSu/m6-Oqem8T+oE--$0a/k]uj9EwsG>%veR*" - "hv^BFpQj:K'#SJ,sB-'#](j.Lg92rTw-*n%@/;39rrJF,l#qV%OrtBeC6/,;qB3ebNW[?,Hqj2L.1NP&GjUR=1D8QaS3Up&@*9wP?+lo7b?@%'k4`p0Z$22%K3+iCZj?XJN4Nm&+YF]u" - "@-W$U%VEQ/,,>>#)D#%8cY#YZ?=,`Wdxu/ae&#" - "w6)R89tI#6@s'(6Bf7a&?S=^ZI_kS&ai`&=tE72L_D,;^R)7[$so8lKN%5/$(vdfq7+ebA#" - "u1p]ovUKW&Y%q]'>$1@-[xfn$7ZTp7mM,G,Ko7a&Gu%G[RMxJs[0MM%wci.LFDK)(%:_i2B5CsR8&9Z&#=mPEnm0f`<&c)QL5uJ#%u%lJj+D-r;BoFDoS97h5g)E#o:&S4weDF,9^Hoe`h*L+_a*NrLW-1pG_&2UdB8" - "6e%B/:=>)N4xeW.*wft-;$'58-ESqr#U`'6AQ]m&6/`Z>#S?YY#Vc;r7U2&326d=w&H####?TZ`*4?&.MK?LP8Vxg>$[QXc%QJv92.(Db*B)gb*BM9dM*hJMAo*c&#" - "b0v=Pjer]$gG&JXDf->'StvU7505l9$AFvgYRI^&<^b68?j#q9QX4SM'RO#&sL1IM.rJfLUAj221]d##DW=m83u5;'bYx,*Sl0hL(W;;$doB&O/TQ:(Z^xBdLjLV#*8U_72Lh+2Q8Cj0i:6hp&$C/:p(HK>T8Y[gHQ4`4)'$Ab(Nof%V'8hL&#SfD07&6D@M.*J:;$-rv29'M]8qMv-tLp,'886iaC=Hb*YJoKJ,(j%K=H`K.v9HggqBIiZu'QvBT.#=)0ukruV&.)3=(^1`o*Pj4<-#MJ+gLq9-##@HuZPN0]u:h7.T..G:;$/Usj(T7`Q8tT72LnYl<-qx8;-HV7Q-&Xdx%1a,hC=0u+HlsV>nuIQL-5" - "_>@kXQtMacfD.m-VAb8;IReM3$wf0''hra*so568'Ip&vRs849'MRYSp%:t:h5qSgwpEr$B>Q,;s(C#$)`svQuF$##-D,##,g68@2[T;.XSdN9Qe)rpt._K-#5wF)sP'##p#C0c%-Gb%" - "hd+<-j'Ai*x&&HMkT]C'OSl##5RG[JXaHN;d'uA#x._U;.`PU@(Z3dt4r152@:v,'R.Sj'w#0<-;kPI)FfJ&#AYJ&#//)>-k=m=*XnK$>=)72L]0I%>.G690a:$##<,);?;72#?x9+d;" - "^V'9;jY@;)br#q^YQpx:X#Te$Z^'=-=bGhLf:D6&bNwZ9-ZD#n^9HhLMr5G;']d&6'wYmTFmLq9wI>P(9mI[>kC-ekLC/R&CH+s'B;K-M6$EB%is00:" - "+A4[7xks.LrNk0&E)wILYF@2L'0Nb$+pv<(2.768/FrY&h$^3i&@+G%JT'<-,v`3;_)I9M^AE]CN?Cl2AZg+%4iTpT3$U4O]GKx'm9)b@p7YsvK3w^YR-" - "CdQ*:Ir<($u&)#(&?L9Rg3H)4fiEp^iI9O8KnTj,]H?D*r7'M;PwZ9K0E^k&-cpI;.p/6_vwoFMV<->#%Xi.LxVnrU(4&8/P+:hLSKj$#U%]49t'I:rgMi'FL@a:0Y-uA[39',(vbma*" - "hU%<-SRF`Tt:542R_VV$p@[p8DV[A,?1839FWdFTi1O*H&#(AL8[_P%.M>v^-))qOT*F5Cq0`Ye%+$B6i:7@0IXSsDiWP,##P`%/L-" - "S(qw%sf/@%#B6;/U7K]uZbi^Oc^2n%t<)'mEVE''n`WnJra$^TKvX5B>;_aSEK',(hwa0:i4G?.Bci.(X[?b*($,=-n<.Q%`(X=?+@Am*Js0&=3bh8K]mL69=Lb,OcZV/);TTm8VI;?%OtJ<(b4mq7M6:u?KRdFl*:xP?Yb.5)%w_I?7uk5JC+FS(m#i'k.'a0i)9<7b'fs'59hq$*5Uhv##pi^8+hIEBF`nvo`;'l0.^S1<-wUK2/Coh58KKhLj" - "M=SO*rfO`+qC`W-On.=AJ56>>i2@2LH6A:&5q`?9I3@@'04&p2/LVa*T-4<-i3;M9UvZd+N7>b*eIwg:CC)c<>nO&#$(>.Z-I&J(Q0Hd5Q%7Co-b`-cP)hI;*_F]u`Rb[.j8_Q/<&>uu+VsH$sM9TA%?)(vmJ80),P7E>)tjD%2L=-t#fK[%`v=Q8WlA2);Sa" - ">gXm8YB`1d@K#n]76-a$U,mF%Ul:#/'xoFM9QX-$.QN'>" - "[%$Z$uF6pA6Ki2O5:8w*vP1<-1`[G,)-m#>0`P&#eb#.3i)rtB61(o'$?X3B2Qft^ae_5tKL9MUe9b*sLEQ95C&`=G?@Mj=wh*'3E>=-<)Gt*Iw)'QG:`@I" - "wOf7&]1i'S01B+Ev/Nac#9S;=;YQpg_6U`*kVY39xK,[/6Aj7:'1Bm-_1EYfa1+o&o4hp7KN_Q(OlIo@S%;jVdn0'1h19w,WQhLI)3S#f$2(eb,jr*b;3Vw]*7NH%$c4Vs,eD9>XW8?N]o+(*pgC%/72LV-uW%iewS8W6m2rtCpo'RS1R84=@paTKt)>=%&1[)*vp'u+x,VrwN;&]kuO9JDbg=pO$J*.jVe;u'm0dr9l,<*wMK*Oe=g8lV_KEBFkO'oU]^=[-792#ok,)" - "i]lR8qQ2oA8wcRCZ^7w/Njh;?.stX?Q1>S1q4Bn$)K1<-rGdO'$Wr.Lc.CG)$/*JL4tNR/,SVO3,aUw'DJN:)Ss;wGn9A32ijw%FL+Z0Fn.U9;reSq)bmI32U==5ALuG&#Vf1398/pVo" - "1*c-(aY168o<`JsSbk-,1N;$>0:OUas(3:8Z972LSfF8eb=c-;>SPw7.6hn3m`9^Xkn(r.qS[0;T%&Qc=+STRxX'q1BNk3&*eu2;&8q$&x>Q#Q7^Tf+6<(d%ZVmj2bDi%.3L2n+4W'$P" - "iDDG)g,r%+?,$@?uou5tSe2aN_AQU*'IAO" - "URQ##V^Fv-XFbGM7Fl(N<3DhLGF%q.1rC$#:T__&Pi68%0xi_&[qFJ(77j_&JWoF.V735&T,[R*:xFR*K5>>#`bW-?4Ne_&6Ne_&6Ne_&n`kr-#GJcM6X;uM6X;uM(.a..^2TkL%oR(#" - ";u.T%fAr%4tJ8&><1=GHZ_+m9/#H1F^R#SC#*N=BA9(D?v[UiFY>>^8p,KKF.W]L29uLkLlu/+4T" - "w$)F./^n3+rlo+DB;5sIYGNk+i1t-69Jg--0pao7Sm#K)pdHW&;LuDNH@H>#/X-TI(;P>#,Gc>#0Su>#4`1?#8lC?#xL$#B.`$#F:r$#JF.%#NR@%#R_R%#Vke%#Zww%#_-4^Rh%Sflr-k'MS.o?.5/sWel/wpEM0%3'/1)K^f1-d>G21&v(35>V`39V7A4=onx4" - "A1OY5EI0;6Ibgr6M$HS7Q<)58C5w,;WoA*#[%T*#`1g*#d=#+#hI5+#lUG+#pbY+#tnl+#x$),#&1;,#*=M,#.I`,#2Ur,#6b.-#;w[H#iQtA#m^0B#qjBB#uvTB##-hB#'9$C#+E6C#" - "/QHC#3^ZC#7jmC#;v)D#?,)4kMYD4lVu`4m`:&5niUA5@(A5BA1]PBB:xlBCC=2CDLXMCEUtiCf&0g2'tN?PGT4CPGT4CPGT4CPGT4CPGT4CPGT4CPGT4CP" - "GT4CPGT4CPGT4CPGT4CPGT4CPGT4CP-qekC`.9kEg^+F$kwViFJTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5o,^<-28ZI'O?;xp" - "O?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xp;7q-#lLYI:xvD=#"; -static const char* GetDefaultCompressedFontDataTTFBase85() +#ifndef IMGUI_DISABLE_DEFAULT_FONT + +// File: 'ProggyClean.ttf' (41208 bytes) +// Exported using binary_to_compressed_c.exe -u8 "ProggyClean.ttf" proggy_clean_ttf +static const unsigned int proggy_clean_ttf_compressed_size = 9583; +static const unsigned char proggy_clean_ttf_compressed_data[9583] = { - return proggy_clean_ttf_compressed_data_base85; + 87,188,0,0,0,0,0,0,0,0,160,248,0,4,0,0,55,0,1,0,0,0,12,0,128,0,3,0,64,79,83,47,50,136,235,116,144,0,0,1,72,130,21,44,78,99,109,97,112,2,18,35,117,0,0,3,160,130,19,36,82,99,118,116, + 32,130,23,130,2,33,4,252,130,4,56,2,103,108,121,102,18,175,137,86,0,0,7,4,0,0,146,128,104,101,97,100,215,145,102,211,130,27,32,204,130,3,33,54,104,130,16,39,8,66,1,195,0,0,1,4,130, + 15,59,36,104,109,116,120,138,0,126,128,0,0,1,152,0,0,2,6,108,111,99,97,140,115,176,216,0,0,5,130,30,41,2,4,109,97,120,112,1,174,0,218,130,31,32,40,130,16,44,32,110,97,109,101,37,89, + 187,150,0,0,153,132,130,19,44,158,112,111,115,116,166,172,131,239,0,0,155,36,130,51,44,210,112,114,101,112,105,2,1,18,0,0,4,244,130,47,32,8,132,203,46,1,0,0,60,85,233,213,95,15,60, + 245,0,3,8,0,131,0,34,183,103,119,130,63,43,0,0,189,146,166,215,0,0,254,128,3,128,131,111,130,241,33,2,0,133,0,32,1,130,65,38,192,254,64,0,0,3,128,131,16,130,5,32,1,131,7,138,3,33,2, + 0,130,17,36,1,1,0,144,0,130,121,130,23,38,2,0,8,0,64,0,10,130,9,32,118,130,9,130,6,32,0,130,59,33,1,144,131,200,35,2,188,2,138,130,16,32,143,133,7,37,1,197,0,50,2,0,131,0,33,4,9,131, + 5,145,3,43,65,108,116,115,0,64,0,0,32,172,8,0,131,0,35,5,0,1,128,131,77,131,3,33,3,128,191,1,33,1,128,130,184,35,0,0,128,0,130,3,131,11,32,1,130,7,33,0,128,131,1,32,1,136,9,32,0,132, + 15,135,5,32,1,131,13,135,27,144,35,32,1,149,25,131,21,32,0,130,0,32,128,132,103,130,35,132,39,32,0,136,45,136,97,133,17,130,5,33,0,0,136,19,34,0,128,1,133,13,133,5,32,128,130,15,132, + 131,32,3,130,5,32,3,132,27,144,71,32,0,133,27,130,29,130,31,136,29,131,63,131,3,65,63,5,132,5,132,205,130,9,33,0,0,131,9,137,119,32,3,132,19,138,243,130,55,32,1,132,35,135,19,131,201, + 136,11,132,143,137,13,130,41,32,0,131,3,144,35,33,128,0,135,1,131,223,131,3,141,17,134,13,136,63,134,15,136,53,143,15,130,96,33,0,3,131,4,130,3,34,28,0,1,130,5,34,0,0,76,130,17,131, + 9,36,28,0,4,0,48,130,17,46,8,0,8,0,2,0,0,0,127,0,255,32,172,255,255,130,9,34,0,0,129,132,9,130,102,33,223,213,134,53,132,22,33,1,6,132,6,64,4,215,32,129,165,216,39,177,0,1,141,184, + 1,255,133,134,45,33,198,0,193,1,8,190,244,1,28,1,158,2,20,2,136,2,252,3,20,3,88,3,156,3,222,4,20,4,50,4,80,4,98,4,162,5,22,5,102,5,188,6,18,6,116,6,214,7,56,7,126,7,236,8,78,8,108, + 8,150,8,208,9,16,9,74,9,136,10,22,10,128,11,4,11,86,11,200,12,46,12,130,12,234,13,94,13,164,13,234,14,80,14,150,15,40,15,176,16,18,16,116,16,224,17,82,17,182,18,4,18,110,18,196,19, + 76,19,172,19,246,20,88,20,174,20,234,21,64,21,128,21,166,21,184,22,18,22,126,22,198,23,52,23,142,23,224,24,86,24,186,24,238,25,54,25,150,25,212,26,72,26,156,26,240,27,92,27,200,28, + 4,28,76,28,150,28,234,29,42,29,146,29,210,30,64,30,142,30,224,31,36,31,118,31,166,31,166,32,16,130,1,52,46,32,138,32,178,32,200,33,20,33,116,33,152,33,238,34,98,34,134,35,12,130,1, + 33,128,35,131,1,60,152,35,176,35,216,36,0,36,74,36,104,36,144,36,174,37,6,37,96,37,130,37,248,37,248,38,88,38,170,130,1,8,190,216,39,64,39,154,40,10,40,104,40,168,41,14,41,32,41,184, + 41,248,42,54,42,96,42,96,43,2,43,42,43,94,43,172,43,230,44,32,44,52,44,154,45,40,45,92,45,120,45,170,45,232,46,38,46,166,47,38,47,182,47,244,48,94,48,200,49,62,49,180,50,30,50,158, + 51,30,51,130,51,238,52,92,52,206,53,58,53,134,53,212,54,38,54,114,54,230,55,118,55,216,56,58,56,166,57,18,57,116,57,174,58,46,58,154,59,6,59,124,59,232,60,58,60,150,61,34,61,134,61, + 236,62,86,62,198,63,42,63,154,64,18,64,106,64,208,65,54,65,162,66,8,66,64,66,122,66,184,66,240,67,98,67,204,68,42,68,138,68,238,69,88,69,182,69,226,70,84,70,180,71,20,71,122,71,218, + 72,84,72,198,73,64,0,36,70,21,8,8,77,3,0,7,0,11,0,15,0,19,0,23,0,27,0,31,0,35,0,39,0,43,0,47,0,51,0,55,0,59,0,63,0,67,0,71,0,75,0,79,0,83,0,87,0,91,0,95,0,99,0,103,0,107,0,111,0,115, + 0,119,0,123,0,127,0,131,0,135,0,139,0,143,0,0,17,53,51,21,49,150,3,32,5,130,23,32,33,130,3,211,7,151,115,32,128,133,0,37,252,128,128,2,128,128,190,5,133,74,32,4,133,6,206,5,42,0,7, + 1,128,0,0,2,0,4,0,0,65,139,13,37,0,1,53,51,21,7,146,3,32,3,130,19,32,1,141,133,32,3,141,14,131,13,38,255,0,128,128,0,6,1,130,84,35,2,128,4,128,140,91,132,89,32,51,65,143,6,139,7,33, + 1,0,130,57,32,254,130,3,32,128,132,4,32,4,131,14,138,89,35,0,0,24,0,130,0,33,3,128,144,171,66,55,33,148,115,65,187,19,32,5,130,151,143,155,163,39,32,1,136,182,32,253,134,178,132,7, + 132,200,145,17,32,3,65,48,17,165,17,39,0,0,21,0,128,255,128,3,65,175,17,65,3,27,132,253,131,217,139,201,155,233,155,27,131,67,131,31,130,241,33,255,0,131,181,137,232,132,15,132,4,138, + 247,34,255,0,128,179,238,32,0,130,0,32,20,65,239,48,33,0,19,67,235,10,32,51,65,203,14,65,215,11,32,7,154,27,135,39,32,33,130,35,33,128,128,130,231,32,253,132,231,32,128,132,232,34, + 128,128,254,133,13,136,8,32,253,65,186,5,130,36,130,42,176,234,133,231,34,128,0,0,66,215,44,33,0,1,68,235,6,68,211,19,32,49,68,239,14,139,207,139,47,66,13,7,32,51,130,47,33,1,0,130, + 207,35,128,128,1,0,131,222,131,5,130,212,130,6,131,212,32,0,130,10,133,220,130,233,130,226,32,254,133,255,178,233,39,3,1,128,3,0,2,0,4,68,15,7,68,99,12,130,89,130,104,33,128,4,133, + 93,130,10,38,0,0,11,1,0,255,0,68,63,16,70,39,9,66,215,8,32,7,68,77,6,68,175,14,32,29,68,195,6,132,7,35,2,0,128,255,131,91,132,4,65,178,5,141,111,67,129,23,165,135,140,107,142,135,33, + 21,5,69,71,6,131,7,33,1,0,140,104,132,142,130,4,137,247,140,30,68,255,12,39,11,0,128,0,128,3,0,3,69,171,15,67,251,7,65,15,8,66,249,11,65,229,7,67,211,7,66,13,7,35,1,128,128,254,133, + 93,32,254,131,145,132,4,132,18,32,2,151,128,130,23,34,0,0,9,154,131,65,207,8,68,107,15,68,51,7,32,7,70,59,7,135,121,130,82,32,128,151,111,41,0,0,4,0,128,255,0,1,128,1,137,239,33,0, + 37,70,145,10,65,77,10,65,212,14,37,0,0,0,5,0,128,66,109,5,70,123,10,33,0,19,72,33,18,133,237,70,209,11,33,0,2,130,113,137,119,136,115,33,1,0,133,43,130,5,34,0,0,10,69,135,6,70,219, + 13,66,155,7,65,9,12,66,157,11,66,9,11,32,7,130,141,132,252,66,151,9,137,9,66,15,30,36,0,20,0,128,0,130,218,71,11,42,68,51,8,65,141,7,73,19,15,69,47,23,143,39,66,81,7,32,1,66,55,6,34, + 1,128,128,68,25,5,69,32,6,137,6,136,25,32,254,131,42,32,3,66,88,26,148,26,32,0,130,0,32,14,164,231,70,225,12,66,233,7,67,133,19,71,203,15,130,161,32,255,130,155,32,254,139,127,134, + 12,164,174,33,0,15,164,159,33,59,0,65,125,20,66,25,7,32,5,68,191,6,66,29,7,144,165,65,105,9,35,128,128,255,0,137,2,133,182,164,169,33,128,128,197,171,130,155,68,235,7,32,21,70,77,19, + 66,21,10,68,97,8,66,30,5,66,4,43,34,0,17,0,71,19,41,65,253,20,71,25,23,65,91,15,65,115,7,34,2,128,128,66,9,8,130,169,33,1,0,66,212,13,132,28,72,201,43,35,0,0,0,18,66,27,38,76,231,5, + 68,157,20,135,157,32,7,68,185,13,65,129,28,66,20,5,32,253,66,210,11,65,128,49,133,61,32,0,65,135,6,74,111,37,72,149,12,66,203,19,65,147,19,68,93,7,68,85,8,76,4,5,33,255,0,133,129,34, + 254,0,128,68,69,8,181,197,34,0,0,12,65,135,32,65,123,20,69,183,27,133,156,66,50,5,72,87,10,67,137,32,33,0,19,160,139,78,251,13,68,55,20,67,119,19,65,91,36,69,177,15,32,254,143,16,65, + 98,53,32,128,130,0,32,0,66,43,54,70,141,23,66,23,15,131,39,69,47,11,131,15,70,129,19,74,161,9,36,128,255,0,128,254,130,153,65,148,32,67,41,9,34,0,0,4,79,15,5,73,99,10,71,203,8,32,3, + 72,123,6,72,43,8,32,2,133,56,131,99,130,9,34,0,0,6,72,175,5,73,159,14,144,63,135,197,132,189,133,66,33,255,0,73,6,7,70,137,12,35,0,0,0,10,130,3,73,243,25,67,113,12,65,73,7,69,161,7, + 138,7,37,21,2,0,128,128,254,134,3,73,116,27,33,128,128,130,111,39,12,0,128,1,0,3,128,2,72,219,21,35,43,0,47,0,67,47,20,130,111,33,21,1,68,167,13,81,147,8,133,230,32,128,77,73,6,32, + 128,131,142,134,18,130,6,32,255,75,18,12,131,243,37,128,0,128,3,128,3,74,231,21,135,123,32,29,134,107,135,7,32,21,74,117,7,135,7,134,96,135,246,74,103,23,132,242,33,0,10,67,151,28, + 67,133,20,66,141,11,131,11,32,3,77,71,6,32,128,130,113,32,1,81,4,6,134,218,66,130,24,131,31,34,0,26,0,130,0,77,255,44,83,15,11,148,155,68,13,7,32,49,78,231,18,79,7,11,73,243,11,32, + 33,65,187,10,130,63,65,87,8,73,239,19,35,0,128,1,0,131,226,32,252,65,100,6,32,128,139,8,33,1,0,130,21,32,253,72,155,44,73,255,20,32,128,71,67,8,81,243,39,67,15,20,74,191,23,68,121, + 27,32,1,66,150,6,32,254,79,19,11,131,214,32,128,130,215,37,2,0,128,253,0,128,136,5,65,220,24,147,212,130,210,33,0,24,72,219,42,84,255,13,67,119,16,69,245,19,72,225,19,65,3,15,69,93, + 19,131,55,132,178,71,115,14,81,228,6,142,245,33,253,0,132,43,172,252,65,16,11,75,219,8,65,219,31,66,223,24,75,223,10,33,29,1,80,243,10,66,175,8,131,110,134,203,133,172,130,16,70,30, + 7,164,183,130,163,32,20,65,171,48,65,163,36,65,143,23,65,151,19,65,147,13,65,134,17,133,17,130,216,67,114,5,164,217,65,137,12,72,147,48,79,71,19,74,169,22,80,251,8,65,173,7,66,157, + 15,74,173,15,32,254,65,170,8,71,186,45,72,131,6,77,143,40,187,195,152,179,65,123,38,68,215,57,68,179,15,65,85,7,69,187,14,32,21,66,95,15,67,19,25,32,1,83,223,6,32,2,76,240,7,77,166, + 43,65,8,5,130,206,32,0,67,39,54,143,167,66,255,19,82,193,11,151,47,85,171,5,67,27,17,132,160,69,172,11,69,184,56,66,95,6,33,12,1,130,237,32,2,68,179,27,68,175,16,80,135,15,72,55,7, + 71,87,12,73,3,12,132,12,66,75,32,76,215,5,169,139,147,135,148,139,81,12,12,81,185,36,75,251,7,65,23,27,76,215,9,87,165,12,65,209,15,72,157,7,65,245,31,32,128,71,128,6,32,1,82,125,5, + 34,0,128,254,131,169,32,254,131,187,71,180,9,132,27,32,2,88,129,44,32,0,78,47,40,65,79,23,79,171,14,32,21,71,87,8,72,15,14,65,224,33,130,139,74,27,62,93,23,7,68,31,7,75,27,7,139,15, + 74,3,7,74,23,27,65,165,11,65,177,15,67,123,5,32,1,130,221,32,252,71,96,5,74,12,12,133,244,130,25,34,1,0,128,130,2,139,8,93,26,8,65,9,32,65,57,14,140,14,32,0,73,79,67,68,119,11,135, + 11,32,51,90,75,14,139,247,65,43,7,131,19,139,11,69,159,11,65,247,6,36,1,128,128,253,0,90,71,9,33,1,0,132,14,32,128,89,93,14,69,133,6,130,44,131,30,131,6,65,20,56,33,0,16,72,179,40, + 75,47,12,65,215,19,74,95,19,65,43,11,131,168,67,110,5,75,23,17,69,106,6,75,65,5,71,204,43,32,0,80,75,47,71,203,15,159,181,68,91,11,67,197,7,73,101,13,68,85,6,33,128,128,130,214,130, + 25,32,254,74,236,48,130,194,37,0,18,0,128,255,128,77,215,40,65,139,64,32,51,80,159,10,65,147,39,130,219,84,212,43,130,46,75,19,97,74,33,11,65,201,23,65,173,31,33,1,0,79,133,6,66,150, + 5,67,75,48,85,187,6,70,207,37,32,71,87,221,13,73,163,14,80,167,15,132,15,83,193,19,82,209,8,78,99,9,72,190,11,77,110,49,89,63,5,80,91,35,99,63,32,70,235,23,81,99,10,69,148,10,65,110, + 36,32,0,65,99,47,95,219,11,68,171,51,66,87,7,72,57,7,74,45,17,143,17,65,114,50,33,14,0,65,111,40,159,195,98,135,15,35,7,53,51,21,100,78,9,95,146,16,32,254,82,114,6,32,128,67,208,37, + 130,166,99,79,58,32,17,96,99,14,72,31,19,72,87,31,82,155,7,67,47,14,32,21,131,75,134,231,72,51,17,72,78,8,133,8,80,133,6,33,253,128,88,37,9,66,124,36,72,65,12,134,12,71,55,43,66,139, + 27,85,135,10,91,33,12,65,35,11,66,131,11,71,32,8,90,127,6,130,244,71,76,11,168,207,33,0,12,66,123,32,32,0,65,183,15,68,135,11,66,111,7,67,235,11,66,111,15,32,254,97,66,12,160,154,67, + 227,52,80,33,15,87,249,15,93,45,31,75,111,12,93,45,11,77,99,9,160,184,81,31,12,32,15,98,135,30,104,175,7,77,249,36,69,73,15,78,5,12,32,254,66,151,19,34,128,128,4,87,32,12,149,35,133, + 21,96,151,31,32,19,72,35,5,98,173,15,143,15,32,21,143,99,158,129,33,0,0,65,35,52,65,11,15,147,15,98,75,11,33,1,0,143,151,132,15,32,254,99,200,37,132,43,130,4,39,0,10,0,128,1,128,3, + 0,104,151,14,97,187,20,69,131,15,67,195,11,87,227,7,33,128,128,132,128,33,254,0,68,131,9,65,46,26,42,0,0,0,7,0,0,255,128,3,128,0,88,223,15,33,0,21,89,61,22,66,209,12,65,2,12,37,0,2, + 1,0,3,128,101,83,8,36,0,1,53,51,29,130,3,34,21,1,0,66,53,8,32,0,68,215,6,100,55,25,107,111,9,66,193,11,72,167,8,73,143,31,139,31,33,1,0,131,158,32,254,132,5,33,253,128,65,16,9,133, + 17,89,130,25,141,212,33,0,0,93,39,8,90,131,25,93,39,14,66,217,6,106,179,8,159,181,71,125,15,139,47,138,141,87,11,14,76,23,14,65,231,26,140,209,66,122,8,81,179,5,101,195,26,32,47,74, + 75,13,69,159,11,83,235,11,67,21,16,136,167,131,106,130,165,130,15,32,128,101,90,24,134,142,32,0,65,103,51,108,23,11,101,231,15,75,173,23,74,237,23,66,15,6,66,46,17,66,58,17,65,105, + 49,66,247,55,71,179,12,70,139,15,86,229,7,84,167,15,32,1,95,72,12,89,49,6,33,128,128,65,136,38,66,30,9,32,0,100,239,7,66,247,29,70,105,20,65,141,19,69,81,15,130,144,32,128,83,41,5, + 32,255,131,177,68,185,5,133,126,65,97,37,32,0,130,0,33,21,0,130,55,66,195,28,67,155,13,34,79,0,83,66,213,13,73,241,19,66,59,19,65,125,11,135,201,66,249,16,32,128,66,44,11,66,56,17, + 68,143,8,68,124,38,67,183,12,96,211,9,65,143,29,112,171,5,32,0,68,131,63,34,33,53,51,71,121,11,32,254,98,251,16,32,253,74,231,10,65,175,37,133,206,37,0,0,8,1,0,0,107,123,11,113,115, + 9,33,0,1,130,117,131,3,73,103,7,66,51,18,66,44,5,133,75,70,88,5,32,254,65,39,12,68,80,9,34,12,0,128,107,179,28,68,223,6,155,111,86,147,15,32,2,131,82,141,110,33,254,0,130,15,32,4,103, + 184,15,141,35,87,176,5,83,11,5,71,235,23,114,107,11,65,189,16,70,33,15,86,153,31,135,126,86,145,30,65,183,41,32,0,130,0,32,10,65,183,24,34,35,0,39,67,85,9,65,179,15,143,15,33,1,0,65, + 28,17,157,136,130,123,32,20,130,3,32,0,97,135,24,115,167,19,80,71,12,32,51,110,163,14,78,35,19,131,19,155,23,77,229,8,78,9,17,151,17,67,231,46,94,135,8,73,31,31,93,215,56,82,171,25, + 72,77,8,162,179,169,167,99,131,11,69,85,19,66,215,15,76,129,13,68,115,22,72,79,35,67,113,5,34,0,0,19,70,31,46,65,89,52,73,223,15,85,199,33,95,33,8,132,203,73,29,32,67,48,16,177,215, + 101,13,15,65,141,43,69,141,15,75,89,5,70,0,11,70,235,21,178,215,36,10,0,128,0,0,71,207,24,33,0,19,100,67,6,80,215,11,66,67,7,80,43,12,71,106,7,80,192,5,65,63,5,66,217,26,33,0,13,156, + 119,68,95,5,72,233,12,134,129,85,81,11,76,165,20,65,43,8,73,136,8,75,10,31,38,128,128,0,0,0,13,1,130,4,32,3,106,235,29,114,179,12,66,131,23,32,7,77,133,6,67,89,12,131,139,116,60,9, + 89,15,37,32,0,74,15,7,103,11,22,65,35,5,33,55,0,93,81,28,67,239,23,78,85,5,107,93,14,66,84,17,65,193,26,74,183,10,66,67,34,143,135,79,91,15,32,7,117,111,8,75,56,9,84,212,9,154,134, + 32,0,130,0,32,18,130,3,70,171,41,83,7,16,70,131,19,84,191,15,84,175,19,84,167,30,84,158,12,154,193,68,107,15,33,0,0,65,79,42,65,71,7,73,55,7,118,191,16,83,180,9,32,255,76,166,9,154, + 141,32,0,130,0,69,195,52,65,225,15,151,15,75,215,31,80,56,10,68,240,17,100,32,9,70,147,39,65,93,12,71,71,41,92,85,15,84,135,23,78,35,15,110,27,10,84,125,8,107,115,29,136,160,38,0,0, + 14,0,128,255,0,82,155,24,67,239,8,119,255,11,69,131,11,77,29,6,112,31,8,134,27,105,203,8,32,2,75,51,11,75,195,12,74,13,29,136,161,37,128,0,0,0,11,1,130,163,82,115,8,125,191,17,69,35, + 12,74,137,15,143,15,32,1,65,157,12,136,12,161,142,65,43,40,65,199,6,65,19,24,102,185,11,76,123,11,99,6,12,135,12,32,254,130,8,161,155,101,23,9,39,8,0,0,1,128,3,128,2,78,63,17,72,245, + 12,67,41,11,90,167,9,32,128,97,49,9,32,128,109,51,14,132,97,81,191,8,130,97,125,99,12,121,35,9,127,75,15,71,79,12,81,151,23,87,97,7,70,223,15,80,245,16,105,97,15,32,254,113,17,6,32, + 128,130,8,105,105,8,76,122,18,65,243,21,74,63,7,38,4,1,0,255,0,2,0,119,247,28,133,65,32,255,141,91,35,0,0,0,16,67,63,36,34,59,0,63,77,59,9,119,147,11,143,241,66,173,15,66,31,11,67, + 75,8,81,74,16,32,128,131,255,87,181,42,127,43,5,34,255,128,2,120,235,11,37,19,0,23,0,0,37,109,191,14,118,219,7,127,43,14,65,79,14,35,0,0,0,3,73,91,5,130,5,38,3,0,7,0,11,0,0,70,205, + 11,88,221,12,32,0,73,135,7,87,15,22,73,135,10,79,153,15,97,71,19,65,49,11,32,1,131,104,121,235,11,80,65,11,142,179,144,14,81,123,46,32,1,88,217,5,112,5,8,65,201,15,83,29,15,122,147, + 11,135,179,142,175,143,185,67,247,39,66,199,7,35,5,0,128,3,69,203,15,123,163,12,67,127,7,130,119,71,153,10,141,102,70,175,8,32,128,121,235,30,136,89,100,191,11,116,195,11,111,235,15, + 72,39,7,32,2,97,43,5,132,5,94,67,8,131,8,125,253,10,32,3,65,158,16,146,16,130,170,40,0,21,0,128,0,0,3,128,5,88,219,15,24,64,159,32,135,141,65,167,15,68,163,10,97,73,49,32,255,82,58, + 7,93,80,8,97,81,16,24,67,87,52,34,0,0,5,130,231,33,128,2,80,51,13,65,129,8,113,61,6,132,175,65,219,5,130,136,77,152,17,32,0,95,131,61,70,215,6,33,21,51,90,53,10,78,97,23,105,77,31, + 65,117,7,139,75,24,68,195,9,24,64,22,9,33,0,128,130,11,33,128,128,66,25,5,121,38,5,134,5,134,45,66,40,36,66,59,18,34,128,0,0,66,59,81,135,245,123,103,19,120,159,19,77,175,12,33,255, + 0,87,29,10,94,70,21,66,59,54,39,3,1,128,3,0,2,128,4,24,65,7,15,66,47,7,72,98,12,37,0,0,0,3,1,0,24,65,55,21,131,195,32,1,67,178,6,33,4,0,77,141,8,32,6,131,47,74,67,16,24,69,3,20,24, + 65,251,7,133,234,130,229,94,108,17,35,0,0,6,0,141,175,86,59,5,162,79,85,166,8,70,112,13,32,13,24,64,67,26,24,71,255,7,123,211,12,80,121,11,69,215,15,66,217,11,69,71,10,131,113,132, + 126,119,90,9,66,117,19,132,19,32,0,130,0,24,64,47,59,33,7,0,73,227,5,68,243,15,85,13,12,76,37,22,74,254,15,130,138,33,0,4,65,111,6,137,79,65,107,16,32,1,77,200,6,34,128,128,3,75,154, + 12,37,0,16,0,0,2,0,104,115,36,140,157,68,67,19,68,51,15,106,243,15,134,120,70,37,10,68,27,10,140,152,65,121,24,32,128,94,155,7,67,11,8,24,74,11,25,65,3,12,83,89,18,82,21,37,67,200, + 5,130,144,24,64,172,12,33,4,0,134,162,74,80,14,145,184,32,0,130,0,69,251,20,32,19,81,243,5,82,143,8,33,5,53,89,203,5,133,112,79,109,15,33,0,21,130,71,80,175,41,36,75,0,79,0,83,121, + 117,9,87,89,27,66,103,11,70,13,15,75,191,11,135,67,87,97,20,109,203,5,69,246,8,108,171,5,78,195,38,65,51,13,107,203,11,77,3,17,24,75,239,17,65,229,28,79,129,39,130,175,32,128,123,253, + 7,132,142,24,65,51,15,65,239,41,36,128,128,0,0,13,65,171,5,66,163,28,136,183,118,137,11,80,255,15,67,65,7,74,111,8,32,0,130,157,32,253,24,76,35,10,103,212,5,81,175,9,69,141,7,66,150, + 29,131,158,24,75,199,28,124,185,7,76,205,15,68,124,14,32,3,123,139,16,130,16,33,128,128,108,199,6,33,0,3,65,191,35,107,11,6,73,197,11,24,70,121,15,83,247,15,24,70,173,23,69,205,14, + 32,253,131,140,32,254,136,4,94,198,9,32,3,78,4,13,66,127,13,143,13,32,0,130,0,33,16,0,24,69,59,39,109,147,12,76,253,19,24,69,207,15,69,229,15,130,195,71,90,10,139,10,130,152,73,43, + 40,91,139,10,65,131,37,35,75,0,79,0,84,227,12,143,151,68,25,15,80,9,23,95,169,11,34,128,2,128,112,186,5,130,6,83,161,19,76,50,6,130,37,65,145,44,110,83,5,32,16,67,99,6,71,67,15,76, + 55,17,140,215,67,97,23,76,69,15,77,237,11,104,211,23,77,238,11,65,154,43,33,0,10,83,15,28,83,13,20,67,145,19,67,141,14,97,149,21,68,9,15,86,251,5,66,207,5,66,27,37,82,1,23,127,71,12, + 94,235,10,110,175,24,98,243,15,132,154,132,4,24,66,69,10,32,4,67,156,43,130,198,35,2,1,0,4,75,27,9,69,85,9,95,240,7,32,128,130,35,32,28,66,43,40,24,82,63,23,83,123,12,72,231,15,127, + 59,23,116,23,19,117,71,7,24,77,99,15,67,111,15,71,101,8,36,2,128,128,252,128,127,60,11,32,1,132,16,130,18,141,24,67,107,9,32,3,68,194,15,175,15,38,0,11,0,128,1,128,2,80,63,25,32,0, + 24,65,73,11,69,185,15,83,243,16,32,0,24,81,165,8,130,86,77,35,6,155,163,88,203,5,24,66,195,30,70,19,19,24,80,133,15,32,1,75,211,8,32,254,108,133,8,79,87,20,65,32,9,41,0,0,7,0,128,0, + 0,2,128,2,68,87,15,66,1,16,92,201,16,24,76,24,17,133,17,34,128,0,30,66,127,64,34,115,0,119,73,205,9,66,43,11,109,143,15,24,79,203,11,90,143,15,131,15,155,31,65,185,15,86,87,11,35,128, + 128,253,0,69,7,6,130,213,33,1,0,119,178,15,142,17,66,141,74,83,28,6,36,7,0,0,4,128,82,39,18,76,149,12,67,69,21,32,128,79,118,15,32,0,130,0,32,8,131,206,32,2,79,83,9,100,223,14,102, + 113,23,115,115,7,24,65,231,12,130,162,32,4,68,182,19,130,102,93,143,8,69,107,29,24,77,255,12,143,197,72,51,7,76,195,15,132,139,85,49,15,130,152,131,18,71,81,23,70,14,11,36,0,10,0,128, + 2,69,59,9,89,151,15,66,241,11,76,165,12,71,43,15,75,49,13,65,12,23,132,37,32,0,179,115,130,231,95,181,16,132,77,32,254,67,224,8,65,126,20,79,171,8,32,2,89,81,5,75,143,6,80,41,8,34, + 2,0,128,24,81,72,9,32,0,130,0,35,17,0,0,255,77,99,39,95,65,36,67,109,15,24,69,93,11,77,239,5,95,77,23,35,128,1,0,128,24,86,7,8,132,167,32,2,69,198,41,130,202,33,0,26,120,75,44,24,89, + 51,15,71,243,12,70,239,11,24,84,3,11,66,7,11,71,255,10,32,21,69,155,35,88,151,12,32,128,74,38,10,65,210,8,74,251,5,65,226,5,75,201,13,32,3,65,9,41,146,41,40,0,0,0,9,1,0,1,0,2,91,99, + 19,32,35,106,119,13,70,219,15,83,239,12,137,154,32,2,67,252,19,36,128,0,0,4,1,130,196,32,2,130,8,91,107,8,32,0,135,81,24,73,211,8,132,161,73,164,13,36,0,8,0,128,2,105,123,26,139,67, + 76,99,15,34,1,0,128,135,76,83,156,20,92,104,8,67,251,30,24,86,47,27,123,207,12,24,86,7,15,71,227,8,32,4,65,20,20,131,127,32,0,130,123,32,0,71,223,26,32,19,90,195,22,71,223,15,84,200, + 6,32,128,133,241,24,84,149,9,67,41,25,36,0,0,0,22,0,88,111,49,32,87,66,21,5,77,3,27,123,75,7,71,143,19,135,183,71,183,19,130,171,74,252,5,131,5,89,87,17,32,1,132,18,130,232,68,11,10, + 33,1,128,70,208,16,66,230,18,147,18,130,254,223,255,75,27,23,65,59,15,135,39,155,255,34,128,128,254,104,92,8,33,0,128,65,32,11,65,1,58,33,26,0,130,0,72,71,18,78,55,17,76,11,19,86,101, + 12,75,223,11,89,15,11,24,76,87,15,75,235,15,131,15,72,95,7,85,71,11,72,115,11,73,64,6,34,1,128,128,66,215,9,34,128,254,128,134,14,33,128,255,67,102,5,32,0,130,16,70,38,11,66,26,57, + 88,11,8,24,76,215,34,78,139,7,95,245,7,32,7,24,73,75,23,32,128,131,167,130,170,101,158,9,82,49,22,118,139,6,32,18,67,155,44,116,187,9,108,55,14,80,155,23,66,131,15,93,77,10,131,168, + 32,128,73,211,12,24,75,187,22,32,4,96,71,20,67,108,19,132,19,120,207,8,32,5,76,79,15,66,111,21,66,95,8,32,3,190,211,111,3,8,211,212,32,20,65,167,44,34,75,0,79,97,59,13,32,33,112,63, + 10,65,147,19,69,39,19,143,39,24,66,71,9,130,224,65,185,43,94,176,12,65,183,24,71,38,8,24,72,167,7,65,191,38,136,235,24,96,167,12,65,203,62,115,131,13,65,208,42,175,235,67,127,6,32, + 4,76,171,29,114,187,5,32,71,65,211,5,65,203,68,72,51,8,164,219,32,0,172,214,71,239,58,78,3,27,66,143,15,77,19,15,147,31,35,33,53,51,21,66,183,10,173,245,66,170,30,150,30,34,0,0,23, + 80,123,54,76,1,16,73,125,15,82,245,11,167,253,24,76,85,12,70,184,5,32,254,131,185,37,254,0,128,1,0,128,133,16,117,158,18,92,27,38,65,3,17,130,251,35,17,0,128,254,24,69,83,39,140,243, + 121,73,19,109,167,7,81,41,15,24,95,175,12,102,227,15,121,96,11,24,95,189,7,32,3,145,171,154,17,24,77,47,9,33,0,5,70,71,37,68,135,7,32,29,117,171,11,69,87,15,24,79,97,19,24,79,149,23, + 131,59,32,1,75,235,5,72,115,11,72,143,7,132,188,71,27,46,131,51,32,0,69,95,6,175,215,32,21,131,167,81,15,19,151,191,151,23,131,215,71,43,5,32,254,24,79,164,24,74,109,8,77,166,13,65, + 176,26,88,162,5,98,159,6,171,219,120,247,6,79,29,8,99,169,10,103,59,19,65,209,35,131,35,91,25,19,112,94,15,83,36,8,173,229,33,20,0,88,75,43,71,31,12,65,191,71,33,1,0,130,203,32,254, + 131,4,68,66,7,67,130,6,104,61,13,173,215,38,13,1,0,0,0,2,128,67,111,28,74,129,16,104,35,19,79,161,16,87,14,7,138,143,132,10,67,62,36,114,115,5,162,151,67,33,16,108,181,15,143,151,67, + 5,5,24,100,242,15,170,153,34,0,0,14,65,51,34,32,55,79,75,9,32,51,74,7,10,65,57,38,132,142,32,254,72,0,14,139,163,32,128,80,254,8,67,158,21,65,63,7,32,4,72,227,27,95,155,12,67,119,19, + 124,91,24,149,154,72,177,34,97,223,8,155,151,24,108,227,15,88,147,16,72,117,19,68,35,11,92,253,15,70,199,15,24,87,209,17,32,2,87,233,7,32,1,24,88,195,10,119,24,8,32,3,81,227,24,65, + 125,21,35,128,128,0,25,76,59,48,24,90,187,9,97,235,12,66,61,11,91,105,19,24,79,141,11,24,79,117,15,24,79,129,27,90,53,13,130,13,32,253,131,228,24,79,133,40,69,70,8,66,137,31,65,33, + 19,96,107,8,68,119,29,66,7,5,68,125,16,65,253,19,65,241,27,24,90,179,13,24,79,143,18,33,128,128,130,246,32,254,130,168,68,154,36,77,51,9,97,47,5,167,195,32,21,131,183,78,239,27,155, + 195,78,231,14,201,196,77,11,6,32,5,73,111,37,97,247,12,77,19,31,155,207,78,215,19,162,212,69,17,14,66,91,19,80,143,57,78,203,39,159,215,32,128,93,134,8,24,80,109,24,66,113,15,169,215, + 66,115,6,32,4,69,63,33,32,0,101,113,7,86,227,35,143,211,36,49,53,51,21,1,77,185,14,65,159,28,69,251,34,67,56,8,33,9,0,24,107,175,25,90,111,12,110,251,11,119,189,24,119,187,34,87,15, + 9,32,4,66,231,37,90,39,7,66,239,8,84,219,15,69,105,23,24,85,27,27,87,31,11,33,1,128,76,94,6,32,1,85,241,7,33,128,128,106,48,10,33,128,128,69,136,11,133,13,24,79,116,49,84,236,8,24, + 91,87,9,32,5,165,255,69,115,12,66,27,15,159,15,24,72,247,12,74,178,5,24,80,64,15,33,0,128,143,17,77,89,51,130,214,24,81,43,7,170,215,74,49,8,159,199,143,31,139,215,69,143,5,32,254, + 24,81,50,35,181,217,84,123,70,143,195,159,15,65,187,16,66,123,7,65,175,15,65,193,29,68,207,39,79,27,5,70,131,6,32,4,68,211,33,33,67,0,83,143,14,159,207,143,31,140,223,33,0,128,24,80, + 82,14,24,93,16,23,32,253,65,195,5,68,227,40,133,214,107,31,7,32,5,67,115,27,87,9,8,107,31,43,66,125,6,32,0,103,177,23,131,127,72,203,36,32,0,110,103,8,155,163,73,135,6,32,19,24,112, + 99,10,65,71,11,73,143,19,143,31,126,195,5,24,85,21,9,24,76,47,14,32,254,24,93,77,36,68,207,11,39,25,0,0,255,128,3,128,4,66,51,37,95,247,13,82,255,24,76,39,19,147,221,66,85,27,24,118, + 7,8,24,74,249,12,76,74,8,91,234,8,67,80,17,131,222,33,253,0,121,30,44,73,0,16,69,15,6,32,0,65,23,38,69,231,12,65,179,6,98,131,16,86,31,27,24,108,157,14,80,160,8,24,65,46,17,33,4,0, + 96,2,18,144,191,65,226,8,68,19,5,171,199,80,9,15,180,199,67,89,5,32,255,24,79,173,28,174,201,24,79,179,50,32,1,24,122,5,10,82,61,10,180,209,83,19,8,32,128,24,80,129,27,111,248,43,131, + 71,24,115,103,8,67,127,41,78,213,24,100,247,19,66,115,39,75,107,5,32,254,165,219,78,170,40,24,112,163,49,32,1,97,203,6,65,173,64,32,0,83,54,7,133,217,88,37,12,32,254,131,28,33,128, + 3,67,71,44,84,183,6,32,5,69,223,33,96,7,7,123,137,16,192,211,24,112,14,9,32,255,67,88,29,68,14,10,84,197,38,33,0,22,116,47,50,32,87,106,99,9,116,49,15,89,225,15,97,231,23,70,41,19, + 82,85,8,93,167,6,32,253,132,236,108,190,7,89,251,5,116,49,58,33,128,128,131,234,32,15,24,74,67,38,70,227,24,24,83,45,23,89,219,12,70,187,12,89,216,19,32,2,69,185,24,141,24,70,143,66, + 24,82,119,56,78,24,10,32,253,133,149,132,6,24,106,233,7,69,198,48,178,203,81,243,12,68,211,15,106,255,23,66,91,15,69,193,7,100,39,10,24,83,72,16,176,204,33,19,0,88,207,45,68,21,12, + 68,17,10,65,157,53,68,17,6,32,254,92,67,10,65,161,25,69,182,43,24,118,91,47,69,183,18,181,209,111,253,12,89,159,8,66,112,12,69,184,45,35,0,0,0,9,24,80,227,26,73,185,16,118,195,15,131, + 15,33,1,0,65,59,15,66,39,27,160,111,66,205,12,148,111,143,110,33,128,128,156,112,24,81,199,8,75,199,23,66,117,20,155,121,32,254,68,126,12,72,213,29,134,239,149,123,89,27,16,148,117, + 65,245,8,24,71,159,14,141,134,134,28,73,51,55,109,77,15,105,131,11,68,67,11,76,169,27,107,209,12,102,174,8,32,128,72,100,18,116,163,56,79,203,11,75,183,44,85,119,19,71,119,23,151,227, + 32,1,93,27,8,65,122,5,77,102,8,110,120,20,66,23,8,66,175,17,66,63,12,133,12,79,35,8,74,235,33,67,149,16,69,243,15,78,57,15,69,235,16,67,177,7,151,192,130,23,67,84,29,141,192,174,187, + 77,67,15,69,11,12,159,187,77,59,10,199,189,24,70,235,50,96,83,19,66,53,23,105,65,19,77,47,12,163,199,66,67,37,78,207,50,67,23,23,174,205,67,228,6,71,107,13,67,22,14,66,85,11,83,187, + 38,124,47,49,95,7,19,66,83,23,67,23,19,24,96,78,17,80,101,16,71,98,40,33,0,7,88,131,22,24,89,245,12,84,45,12,102,213,5,123,12,9,32,2,126,21,14,43,255,0,128,128,0,0,20,0,128,255,128, + 3,126,19,39,32,75,106,51,7,113,129,15,24,110,135,19,126,47,15,115,117,11,69,47,11,32,2,109,76,9,102,109,9,32,128,75,2,10,130,21,32,254,69,47,6,32,3,94,217,47,32,0,65,247,10,69,15,46, + 65,235,31,65,243,15,101,139,10,66,174,14,65,247,16,72,102,28,69,17,14,84,243,9,165,191,88,47,48,66,53,12,32,128,71,108,6,203,193,32,17,75,187,42,73,65,16,65,133,52,114,123,9,167,199, + 69,21,37,86,127,44,75,171,11,180,197,78,213,12,148,200,81,97,46,24,95,243,9,32,4,66,75,33,113,103,9,87,243,36,143,225,24,84,27,31,90,145,8,148,216,67,49,5,24,84,34,14,75,155,27,67, + 52,13,140,13,36,0,20,0,128,255,24,135,99,46,88,59,43,155,249,80,165,7,136,144,71,161,23,32,253,132,33,32,254,88,87,44,136,84,35,128,0,0,21,81,103,5,94,47,44,76,51,12,143,197,151,15, + 65,215,31,24,64,77,13,65,220,20,65,214,14,71,4,40,65,213,13,32,0,130,0,35,21,1,2,0,135,0,34,36,0,72,134,10,36,1,0,26,0,130,134,11,36,2,0,14,0,108,134,11,32,3,138,23,32,4,138,11,34, + 5,0,20,134,33,34,0,0,6,132,23,32,1,134,15,32,18,130,25,133,11,37,1,0,13,0,49,0,133,11,36,2,0,7,0,38,134,11,36,3,0,17,0,45,134,11,32,4,138,35,36,5,0,10,0,62,134,23,32,6,132,23,36,3, + 0,1,4,9,130,87,131,167,133,11,133,167,133,11,133,167,133,11,37,3,0,34,0,122,0,133,11,133,167,133,11,133,167,133,11,133,167,34,50,0,48,130,1,34,52,0,47,134,5,8,49,49,0,53,98,121,32, + 84,114,105,115,116,97,110,32,71,114,105,109,109,101,114,82,101,103,117,108,97,114,84,84,88,32,80,114,111,103,103,121,67,108,101,97,110,84,84,50,48,48,52,47,130,2,53,49,53,0,98,0,121, + 0,32,0,84,0,114,0,105,0,115,0,116,0,97,0,110,130,15,32,71,132,15,36,109,0,109,0,101,130,9,32,82,130,5,36,103,0,117,0,108,130,29,32,114,130,43,34,84,0,88,130,35,32,80,130,25,34,111, + 0,103,130,1,34,121,0,67,130,27,32,101,132,59,32,84,130,31,33,0,0,65,155,9,34,20,0,0,65,11,6,130,8,135,2,33,1,1,130,9,8,120,1,1,2,1,3,1,4,1,5,1,6,1,7,1,8,1,9,1,10,1,11,1,12,1,13,1,14, + 1,15,1,16,1,17,1,18,1,19,1,20,1,21,1,22,1,23,1,24,1,25,1,26,1,27,1,28,1,29,1,30,1,31,1,32,0,3,0,4,0,5,0,6,0,7,0,8,0,9,0,10,0,11,0,12,0,13,0,14,0,15,0,16,0,17,0,18,0,19,0,20,0,21,0, + 22,0,23,0,24,0,25,0,26,0,27,0,28,0,29,0,30,0,31,130,187,8,66,33,0,34,0,35,0,36,0,37,0,38,0,39,0,40,0,41,0,42,0,43,0,44,0,45,0,46,0,47,0,48,0,49,0,50,0,51,0,52,0,53,0,54,0,55,0,56,0, + 57,0,58,0,59,0,60,0,61,0,62,0,63,0,64,0,65,0,66,130,243,9,75,68,0,69,0,70,0,71,0,72,0,73,0,74,0,75,0,76,0,77,0,78,0,79,0,80,0,81,0,82,0,83,0,84,0,85,0,86,0,87,0,88,0,89,0,90,0,91,0, + 92,0,93,0,94,0,95,0,96,0,97,1,33,1,34,1,35,1,36,1,37,1,38,1,39,1,40,1,41,1,42,1,43,1,44,1,45,1,46,1,47,1,48,1,49,1,50,1,51,1,52,1,53,1,54,1,55,1,56,1,57,1,58,1,59,1,60,1,61,1,62,1, + 63,1,64,1,65,0,172,0,163,0,132,0,133,0,189,0,150,0,232,0,134,0,142,0,139,0,157,0,169,0,164,0,239,0,138,0,218,0,131,0,147,0,242,0,243,0,141,0,151,0,136,0,195,0,222,0,241,0,158,0,170, + 0,245,0,244,0,246,0,162,0,173,0,201,0,199,0,174,0,98,0,99,0,144,0,100,0,203,0,101,0,200,0,202,0,207,0,204,0,205,0,206,0,233,0,102,0,211,0,208,0,209,0,175,0,103,0,240,0,145,0,214,0, + 212,0,213,0,104,0,235,0,237,0,137,0,106,0,105,0,107,0,109,0,108,0,110,0,160,0,111,0,113,0,112,0,114,0,115,0,117,0,116,0,118,0,119,0,234,0,120,0,122,0,121,0,123,0,125,0,124,0,184,0, + 161,0,127,0,126,0,128,0,129,0,236,0,238,0,186,14,117,110,105,99,111,100,101,35,48,120,48,48,48,49,141,14,32,50,141,14,32,51,141,14,32,52,141,14,32,53,141,14,32,54,141,14,32,55,141, + 14,32,56,141,14,32,57,141,14,32,97,141,14,32,98,141,14,32,99,141,14,32,100,141,14,32,101,141,14,32,102,140,14,33,49,48,141,14,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49, + 141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,45,49,102,6,100,101,108,101,116,101,4,69,117,114, + 111,140,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236, + 32,56,141,236,32,56,141,236,32,56,65,220,13,32,57,65,220,13,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141, + 239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,35,57,102,0,0,5,250,72,249,98,247, +}; + +static const char* GetDefaultCompressedFontDataTTF(int* out_size) +{ + *out_size = proggy_clean_ttf_compressed_size; + return (const char*)proggy_clean_ttf_compressed_data; } +#endif // #ifndef IMGUI_DISABLE_DEFAULT_FONT #endif // #ifndef IMGUI_DISABLE diff --git a/Amalgam/include/ImGui/imgui_freetype.cpp b/Amalgam/include/ImGui/imgui_freetype.cpp index 23368409..23a870d7 100644 --- a/Amalgam/include/ImGui/imgui_freetype.cpp +++ b/Amalgam/include/ImGui/imgui_freetype.cpp @@ -10,7 +10,7 @@ // 2023/11/13: added support for ImFontConfig::RasterizationDensity field for scaling render density without scaling metrics. // 2023/08/01: added support for SVG fonts, enable by using '#define IMGUI_ENABLE_FREETYPE_LUNASVG'. (#6591) // 2023/01/04: fixed a packing issue which in some occurrences would prevent large amount of glyphs from being packed correctly. -// 2021/08/23: fixed crash when FT_Render_Glyph() fails to render a glyph and returns NULL. +// 2021/08/23: fixed crash when FT_Render_Glyph() fails to render a glyph and returns nullptr. // 2021/03/05: added ImGuiFreeTypeBuilderFlags_Bitmap to load bitmap glyphs. // 2021/03/02: set 'atlas->TexPixelsUseColors = true' to help some backends with deciding of a preferred texture format. // 2021/01/28: added support for color-layered glyphs via ImGuiFreeTypeBuilderFlags_LoadColor (require Freetype 2.10+). @@ -33,7 +33,7 @@ // - For correct results you need to be using sRGB and convert to linear space in the pixel shader output. // - The default dear imgui styles will be impacted by this change (alpha values will need tweaking). -// FIXME: cfg.OversampleH, OversampleV are not supported (but perhaps not so necessary with this rasterizer). +// FIXME: cfg.OversampleH, OversampleV are not supported, but generally not necessary with this rasterizer because Hinting makes everything look better. #ifdef IMGUI_ENABLE_FREETYPE #include "imgui.h" @@ -105,6 +105,9 @@ static FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_ // Code //------------------------------------------------------------------------- +#define FT_CEIL(X) (((X + 63) & -64) / 64) // From SDL_ttf: Handy routines for converting from fixed point +#define FT_SCALEFACTOR 64.0f + namespace { // Glyph metrics: @@ -170,6 +173,7 @@ namespace const FT_Glyph_Metrics* LoadGlyph(uint32_t in_codepoint); const FT_Bitmap* RenderGlyphAndGetInfo(GlyphInfo* out_glyph_info); void BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch, unsigned char* multiply_table = nullptr); + FreeTypeFont() { memset((void*)this, 0, sizeof(*this)); } ~FreeTypeFont() { CloseFont(); } // [Internals] @@ -182,9 +186,6 @@ namespace float InvRasterizationDensity; }; - // From SDL_ttf: Handy routines for converting from fixed point - #define FT_CEIL(X) (((X + 63) & -64) / 64) - bool FreeTypeFont::InitFont(FT_Library ft_library, const ImFontConfig& cfg, unsigned int extra_font_builder_flags) { FT_Error error = FT_New_Memory_Face(ft_library, (uint8_t*)cfg.FontData, (uint32_t)cfg.FontDataSize, (uint32_t)cfg.FontNo, &Face); @@ -316,7 +317,7 @@ namespace out_glyph_info->Height = (int)ft_bitmap->rows; out_glyph_info->OffsetX = Face->glyph->bitmap_left; out_glyph_info->OffsetY = -Face->glyph->bitmap_top; - out_glyph_info->AdvanceX = (float)FT_CEIL(slot->advance.x); + out_glyph_info->AdvanceX = (float)slot->advance.x / FT_SCALEFACTOR; out_glyph_info->IsColored = (ft_bitmap->pixel_mode == FT_PIXEL_MODE_BGRA); return ft_bitmap; @@ -573,6 +574,7 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u // 8. Render/rasterize font characters into the texture int total_surface = 0; int buf_rects_out_n = 0; + const int pack_padding = atlas->TexGlyphPadding; for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) { ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i]; @@ -590,7 +592,6 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply); // Gather the sizes of all rectangles we will need to pack - const int padding = atlas->TexGlyphPadding; for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++) { ImFontBuildSrcGlyphFT& src_glyph = src_tmp.GlyphsList[glyph_i]; @@ -618,11 +619,13 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u buf_bitmap_current_used_bytes += bitmap_size_in_bytes; src_tmp.Font.BlitGlyph(ft_bitmap, src_glyph.BitmapData, src_glyph.Info.Width, multiply_enabled ? multiply_table : nullptr); - src_tmp.Rects[glyph_i].w = (stbrp_coord)(src_glyph.Info.Width + padding); - src_tmp.Rects[glyph_i].h = (stbrp_coord)(src_glyph.Info.Height + padding); + src_tmp.Rects[glyph_i].w = (stbrp_coord)(src_glyph.Info.Width + pack_padding); + src_tmp.Rects[glyph_i].h = (stbrp_coord)(src_glyph.Info.Height + pack_padding); total_surface += src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_i].h; } } + for (int i = 0; i < atlas->CustomRects.Size; i++) + total_surface += (atlas->CustomRects[i].Width + pack_padding) * (atlas->CustomRects[i].Height + pack_padding); // We need a width for the skyline algorithm, any width! // The exact width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. @@ -682,8 +685,6 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) { ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i]; - if (src_tmp.GlyphsCount == 0) - continue; // When merging fonts with MergeMode=true: // - We can have multiple input fonts writing into a same destination font. @@ -694,6 +695,9 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u const float ascent = src_tmp.Font.Info.Ascender; const float descent = src_tmp.Font.Info.Descender; ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent); + + if (src_tmp.GlyphsCount == 0) + continue; const float font_off_x = cfg.GlyphOffset.x; const float font_off_y = cfg.GlyphOffset.y + IM_ROUND(dst_font->Ascent); @@ -964,4 +968,4 @@ static FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_ #endif #endif // #ifndef IMGUI_DISABLE -#endif // #ifdef IMGUI_ENABLE_FREETYPE \ No newline at end of file +#endif \ No newline at end of file diff --git a/Amalgam/include/ImGui/imgui_freetype.h b/Amalgam/include/ImGui/imgui_freetype.h index 74ab1695..6572b154 100644 --- a/Amalgam/include/ImGui/imgui_freetype.h +++ b/Amalgam/include/ImGui/imgui_freetype.h @@ -55,4 +55,4 @@ namespace ImGuiFreeType #endif } -#endif // #ifndef IMGUI_DISABLE \ No newline at end of file +#endif // #ifndef IMGUI_DISABLE diff --git a/Amalgam/include/ImGui/imgui_impl_dx9.cpp b/Amalgam/include/ImGui/imgui_impl_dx9.cpp index f1b069df..2cc8681a 100644 --- a/Amalgam/include/ImGui/imgui_impl_dx9.cpp +++ b/Amalgam/include/ImGui/imgui_impl_dx9.cpp @@ -3,7 +3,8 @@ // Implemented features: // [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID! -// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices. +// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -52,6 +53,7 @@ struct ImGui_ImplDX9_Data LPDIRECT3DTEXTURE9 FontTexture; int VertexBufferSize; int IndexBufferSize; + bool HasRgbaSupport; ImGui_ImplDX9_Data() { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; } }; @@ -291,6 +293,24 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) state_block->Release(); } +static bool ImGui_ImplDX9_CheckFormatSupport(LPDIRECT3DDEVICE9 pDevice, D3DFORMAT format) +{ + LPDIRECT3D9 pd3d = nullptr; + if (pDevice->GetDirect3D(&pd3d) != D3D_OK) + return false; + D3DDEVICE_CREATION_PARAMETERS param = {}; + D3DDISPLAYMODE mode = {}; + if (pDevice->GetCreationParameters(¶m) != D3D_OK || pDevice->GetDisplayMode(0, &mode) != D3D_OK) + { + pd3d->Release(); + return false; + } + // Font texture should support linear filter, color blend and write to render-target + bool support = (pd3d->CheckDeviceFormat(param.AdapterOrdinal, param.DeviceType, mode.Format, D3DUSAGE_DYNAMIC | D3DUSAGE_QUERY_FILTER | D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING, D3DRTYPE_TEXTURE, format)) == D3D_OK; + pd3d->Release(); + return support; +} + bool ImGui_ImplDX9_Init(IDirect3DDevice9* device) { ImGuiIO& io = ImGui::GetIO(); @@ -305,6 +325,7 @@ bool ImGui_ImplDX9_Init(IDirect3DDevice9* device) bd->pd3dDevice = device; bd->pd3dDevice->AddRef(); + bd->HasRgbaSupport = ImGui_ImplDX9_CheckFormatSupport(bd->pd3dDevice, D3DFMT_A8B8G8R8); return true; } @@ -323,22 +344,26 @@ void ImGui_ImplDX9_Shutdown() IM_DELETE(bd); } -static bool ImGui_ImplDX9_CheckFormatSupport(IDirect3DDevice9* pDevice, D3DFORMAT format) +// Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices) +static void ImGui_ImplDX9_CopyTextureRegion(bool tex_use_colors, ImU32* src, int src_pitch, ImU32* dst, int dst_pitch, int w, int h) { - IDirect3D9* pd3d = nullptr; - if (pDevice->GetDirect3D(&pd3d) != D3D_OK) - return false; - D3DDEVICE_CREATION_PARAMETERS param = {}; - D3DDISPLAYMODE mode = {}; - if (pDevice->GetCreationParameters(¶m) != D3D_OK || pDevice->GetDisplayMode(0, &mode) != D3D_OK) +#ifndef IMGUI_USE_BGRA_PACKED_COLOR + ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); + const bool convert_rgba_to_bgra = (!bd->HasRgbaSupport && tex_use_colors); +#else + const bool convert_rgba_to_bgra = false; + IM_UNUSED(tex_use_colors); +#endif + for (int y = 0; y < h; y++) { - pd3d->Release(); - return false; + ImU32* src_p = (ImU32*)((unsigned char*)src + src_pitch * y); + ImU32* dst_p = (ImU32*)((unsigned char*)dst + dst_pitch * y); + if (convert_rgba_to_bgra) + for (int x = w; x > 0; x--, src_p++, dst_p++) // Convert copy + *dst_p = IMGUI_COL_TO_DX9_ARGB(*src_p); + else + memcpy(dst_p, src_p, w * 4); // Raw copy } - // Font texture should support linear filter, color blend and write to render-target - bool support = (pd3d->CheckDeviceFormat(param.AdapterOrdinal, param.DeviceType, mode.Format, D3DUSAGE_DYNAMIC | D3DUSAGE_QUERY_FILTER | D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING, D3DRTYPE_TEXTURE, format)) == D3D_OK; - pd3d->Release(); - return support; } static bool ImGui_ImplDX9_CreateFontsTexture() @@ -350,39 +375,18 @@ static bool ImGui_ImplDX9_CreateFontsTexture() int width, height, bytes_per_pixel; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &bytes_per_pixel); - // Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices) -#ifndef IMGUI_USE_BGRA_PACKED_COLOR - const bool rgba_support = ImGui_ImplDX9_CheckFormatSupport(bd->pd3dDevice, D3DFMT_A8B8G8R8); - if (!rgba_support && io.Fonts->TexPixelsUseColors) - { - ImU32* dst_start = (ImU32*)ImGui::MemAlloc((size_t)width * height * bytes_per_pixel); - for (ImU32* src = (ImU32*)pixels, *dst = dst_start, *dst_end = dst_start + (size_t)width * height; dst < dst_end; src++, dst++) - *dst = IMGUI_COL_TO_DX9_ARGB(*src); - pixels = (unsigned char*)dst_start; - } -#else - const bool rgba_support = false; -#endif - // Upload texture to graphics system bd->FontTexture = nullptr; - if (bd->pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, rgba_support ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &bd->FontTexture, nullptr) < 0) + if (bd->pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, bd->HasRgbaSupport ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &bd->FontTexture, nullptr) < 0) return false; D3DLOCKED_RECT tex_locked_rect; if (bd->FontTexture->LockRect(0, &tex_locked_rect, nullptr, 0) != D3D_OK) return false; - for (int y = 0; y < height; y++) - memcpy((unsigned char*)tex_locked_rect.pBits + (size_t)tex_locked_rect.Pitch * y, pixels + (size_t)width * bytes_per_pixel * y, (size_t)width * bytes_per_pixel); + ImGui_ImplDX9_CopyTextureRegion(io.Fonts->TexPixelsUseColors, (ImU32*)pixels, width * bytes_per_pixel, (ImU32*)tex_locked_rect.pBits, (int)tex_locked_rect.Pitch, width, height); bd->FontTexture->UnlockRect(0); // Store our identifier io.Fonts->SetTexID((ImTextureID)bd->FontTexture); - -#ifndef IMGUI_USE_BGRA_PACKED_COLOR - if (!rgba_support && io.Fonts->TexPixelsUseColors) - ImGui::MemFree(pixels); -#endif - return true; } diff --git a/Amalgam/include/ImGui/imgui_impl_dx9.h b/Amalgam/include/ImGui/imgui_impl_dx9.h index df6a0a01..b693054d 100644 --- a/Amalgam/include/ImGui/imgui_impl_dx9.h +++ b/Amalgam/include/ImGui/imgui_impl_dx9.h @@ -3,7 +3,8 @@ // Implemented features: // [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID! -// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices. +// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. diff --git a/Amalgam/include/ImGui/imgui_impl_win32.cpp b/Amalgam/include/ImGui/imgui_impl_win32.cpp index bbfa1a5b..f1cf6ecc 100644 --- a/Amalgam/include/ImGui/imgui_impl_win32.cpp +++ b/Amalgam/include/ImGui/imgui_impl_win32.cpp @@ -4,9 +4,9 @@ // Implemented features: // [X] Platform: Clipboard support (for Win32 this is actually part of core dear imgui) // [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen. -// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy VK_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set] +// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy VK_* values are obsolete since 1.87 and not supported since 1.91.5] // [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. -// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. +// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -133,12 +133,16 @@ static ImGui_ImplWin32_Data* ImGui_ImplWin32_GetBackendData() { return ImGui::GetCurrentContext() ? (ImGui_ImplWin32_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr; } +static ImGui_ImplWin32_Data* ImGui_ImplWin32_GetBackendData(ImGuiIO& io) +{ + return (ImGui_ImplWin32_Data*)io.BackendPlatformUserData; +} // Functions -static void ImGui_ImplWin32_UpdateKeyboardCodePage() +static void ImGui_ImplWin32_UpdateKeyboardCodePage(ImGuiIO& io) { // Retrieve keyboard code page, required for handling of non-Unicode Windows. - ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); + ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(io); HKL keyboard_layout = ::GetKeyboardLayout(0); LCID keyboard_lcid = MAKELCID(HIWORD(keyboard_layout), SORT_DEFAULT); if (::GetLocaleInfoA(keyboard_lcid, (LOCALE_RETURN_NUMBER | LOCALE_IDEFAULTANSICODEPAGE), (LPSTR)&bd->KeyboardCodePage, sizeof(bd->KeyboardCodePage)) == 0) @@ -168,7 +172,7 @@ static bool ImGui_ImplWin32_InitEx(void* hwnd, bool platform_has_own_dc) bd->TicksPerSecond = perf_frequency; bd->Time = perf_counter; bd->LastMouseCursor = ImGuiMouseCursor_COUNT; - ImGui_ImplWin32_UpdateKeyboardCodePage(); + ImGui_ImplWin32_UpdateKeyboardCodePage(io); // Set platform dependent data in viewport ImGuiViewport* main_viewport = ImGui::GetMainViewport(); @@ -228,13 +232,11 @@ void ImGui_ImplWin32_Shutdown() IM_DELETE(bd); } -static bool ImGui_ImplWin32_UpdateMouseCursor() +static bool ImGui_ImplWin32_UpdateMouseCursor(ImGuiIO& io, ImGuiMouseCursor imgui_cursor) { - ImGuiIO& io = ImGui::GetIO(); if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) return false; - ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor) { // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor @@ -266,42 +268,39 @@ static bool IsVkDown(int vk) return (::GetKeyState(vk) & 0x8000) != 0; } -static void ImGui_ImplWin32_AddKeyEvent(ImGuiKey key, bool down, int native_keycode, int native_scancode = -1) +static void ImGui_ImplWin32_AddKeyEvent(ImGuiIO& io, ImGuiKey key, bool down, int native_keycode, int native_scancode = -1) { - ImGuiIO& io = ImGui::GetIO(); io.AddKeyEvent(key, down); io.SetKeyEventNativeData(key, native_keycode, native_scancode); // To support legacy indexing (<1.87 user code) IM_UNUSED(native_scancode); } -static void ImGui_ImplWin32_ProcessKeyEventsWorkarounds() +static void ImGui_ImplWin32_ProcessKeyEventsWorkarounds(ImGuiIO& io) { // Left & right Shift keys: when both are pressed together, Windows tend to not generate the WM_KEYUP event for the first released one. if (ImGui::IsKeyDown(ImGuiKey_LeftShift) && !IsVkDown(VK_LSHIFT)) - ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftShift, false, VK_LSHIFT); + ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftShift, false, VK_LSHIFT); if (ImGui::IsKeyDown(ImGuiKey_RightShift) && !IsVkDown(VK_RSHIFT)) - ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightShift, false, VK_RSHIFT); + ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_RightShift, false, VK_RSHIFT); // Sometimes WM_KEYUP for Win key is not passed down to the app (e.g. for Win+V on some setups, according to GLFW). if (ImGui::IsKeyDown(ImGuiKey_LeftSuper) && !IsVkDown(VK_LWIN)) - ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftSuper, false, VK_LWIN); + ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftSuper, false, VK_LWIN); if (ImGui::IsKeyDown(ImGuiKey_RightSuper) && !IsVkDown(VK_RWIN)) - ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightSuper, false, VK_RWIN); + ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_RightSuper, false, VK_RWIN); } -static void ImGui_ImplWin32_UpdateKeyModifiers() +static void ImGui_ImplWin32_UpdateKeyModifiers(ImGuiIO& io) { - ImGuiIO& io = ImGui::GetIO(); io.AddKeyEvent(ImGuiMod_Ctrl, IsVkDown(VK_CONTROL)); io.AddKeyEvent(ImGuiMod_Shift, IsVkDown(VK_SHIFT)); io.AddKeyEvent(ImGuiMod_Alt, IsVkDown(VK_MENU)); io.AddKeyEvent(ImGuiMod_Super, IsVkDown(VK_LWIN) || IsVkDown(VK_RWIN)); } -static void ImGui_ImplWin32_UpdateMouseData() +static void ImGui_ImplWin32_UpdateMouseData(ImGuiIO& io) { - ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); - ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(io); IM_ASSERT(bd->hWnd != 0); HWND focused_window = ::GetForegroundWindow(); @@ -328,11 +327,10 @@ static void ImGui_ImplWin32_UpdateMouseData() } // Gamepad navigation mapping -static void ImGui_ImplWin32_UpdateGamepads() +static void ImGui_ImplWin32_UpdateGamepads(ImGuiIO& io) { #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); + ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(io); //if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs. // return; @@ -381,7 +379,9 @@ static void ImGui_ImplWin32_UpdateGamepads() MAP_ANALOG(ImGuiKey_GamepadRStickDown, gamepad.sThumbRY, -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768); #undef MAP_BUTTON #undef MAP_ANALOG -#endif // #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD +#else // #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD + IM_UNUSED(io); +#endif } void ImGui_ImplWin32_NewFrame() @@ -402,25 +402,26 @@ void ImGui_ImplWin32_NewFrame() bd->Time = current_time; // Update OS mouse position - ImGui_ImplWin32_UpdateMouseData(); + ImGui_ImplWin32_UpdateMouseData(io); // Process workarounds for known Windows key handling issues - ImGui_ImplWin32_ProcessKeyEventsWorkarounds(); + ImGui_ImplWin32_ProcessKeyEventsWorkarounds(io); // Update OS mouse cursor with the cursor requested by imgui ImGuiMouseCursor mouse_cursor = io.MouseDrawCursor ? ImGuiMouseCursor_None : ImGui::GetMouseCursor(); if (bd->LastMouseCursor != mouse_cursor) { bd->LastMouseCursor = mouse_cursor; - ImGui_ImplWin32_UpdateMouseCursor(); + ImGui_ImplWin32_UpdateMouseCursor(io, mouse_cursor); } // Update game controllers (if enabled and available) - ImGui_ImplWin32_UpdateGamepads(); + ImGui_ImplWin32_UpdateGamepads(io); } // Map VK_xxx to ImGuiKey_xxx. // Not static to allow third-party code to use that if they want to (but undocumented) +ImGuiKey ImGui_ImplWin32_KeyEventToImGuiKey(WPARAM wParam, LPARAM lParam); ImGuiKey ImGui_ImplWin32_KeyEventToImGuiKey(WPARAM wParam, LPARAM lParam) { // There is no distinct VK_xxx for keypad enter, instead it is VK_RETURN + KF_EXTENDED. @@ -558,22 +559,10 @@ ImGuiKey ImGui_ImplWin32_KeyEventToImGuiKey(WPARAM wParam, LPARAM lParam) #define DBT_DEVNODES_CHANGED 0x0007 #endif -// Win32 message handler (process Win32 mouse/keyboard inputs, etc.) -// Call from your application's message handler. Keep calling your message handler unless this function returns TRUE. -// When implementing your own backend, you can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if Dear ImGui wants to use your inputs. -// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. -// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. -// Generally you may always pass all inputs to Dear ImGui, and hide them from your application based on those two flags. -// PS: In this Win32 handler, we use the capture API (GetCapture/SetCapture/ReleaseCapture) to be able to read mouse coordinates when dragging mouse outside of our window bounds. -// PS: We treat DBLCLK messages as regular mouse down messages, so this code will work on windows classes that have the CS_DBLCLKS flag set. Our own example app code doesn't set this flag. -#if 0 -// Copy this line into your .cpp file to forward declare the function. -extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); -#endif - +// Helper to obtain the source of mouse messages. // See https://learn.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages // Prefer to call this at the top of the message handler to avoid the possibility of other Win32 calls interfering with this. -static ImGuiMouseSource GetMouseSourceFromMessageExtraInfo() +static ImGuiMouseSource ImGui_ImplWin32_GetMouseSourceFromMessageExtraInfo() { LPARAM extra_info = ::GetMessageExtraInfo(); if ((extra_info & 0xFFFFFF80) == 0xFF515700) @@ -583,22 +572,40 @@ static ImGuiMouseSource GetMouseSourceFromMessageExtraInfo() return ImGuiMouseSource_Mouse; } +// Win32 message handler (process Win32 mouse/keyboard inputs, etc.) +// Call from your application's message handler. Keep calling your message handler unless this function returns TRUE. +// When implementing your own backend, you can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if Dear ImGui wants to use your inputs. +// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. +// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. +// Generally you may always pass all inputs to Dear ImGui, and hide them from your application based on those two flags. +// PS: We treat DBLCLK messages as regular mouse down messages, so this code will work on windows classes that have the CS_DBLCLKS flag set. Our own example app code doesn't set this flag. + +// Copy either line into your .cpp file to forward declare the function: +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // Use ImGui::GetCurrentContext() +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ImGuiIO& io); // Doesn't use ImGui::GetCurrentContext() + IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Most backends don't have silent checks like this one, but we need it because WndProc are called early in CreateWindow(). // We silently allow both context or just only backend data to be nullptr. - ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); + if (ImGui::GetCurrentContext() == nullptr) + return 0; + return ImGui_ImplWin32_WndProcHandlerEx(hwnd, msg, wParam, lParam, ImGui::GetIO()); +} + +// This version is in theory thread-safe in the sense that no path should access ImGui::GetCurrentContext(). +IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, ImGuiIO& io) +{ + ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(io); if (bd == nullptr) return 0; - ImGuiIO& io = ImGui::GetIO(); - switch (msg) { case WM_MOUSEMOVE: case WM_NCMOUSEMOVE: { // We need to call TrackMouseEvent in order to receive WM_MOUSELEAVE events - ImGuiMouseSource mouse_source = GetMouseSourceFromMessageExtraInfo(); + ImGuiMouseSource mouse_source = ImGui_ImplWin32_GetMouseSourceFromMessageExtraInfo(); const int area = (msg == WM_MOUSEMOVE) ? 1 : 2; bd->MouseHwnd = hwnd; if (bd->MouseTrackedArea != area) @@ -645,14 +652,14 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA case WM_MBUTTONDOWN: case WM_MBUTTONDBLCLK: case WM_XBUTTONDOWN: case WM_XBUTTONDBLCLK: { - ImGuiMouseSource mouse_source = GetMouseSourceFromMessageExtraInfo(); + ImGuiMouseSource mouse_source = ImGui_ImplWin32_GetMouseSourceFromMessageExtraInfo(); int button = 0; if (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONDBLCLK) { button = 0; } if (msg == WM_RBUTTONDOWN || msg == WM_RBUTTONDBLCLK) { button = 1; } if (msg == WM_MBUTTONDOWN || msg == WM_MBUTTONDBLCLK) { button = 2; } if (msg == WM_XBUTTONDOWN || msg == WM_XBUTTONDBLCLK) { button = (GET_XBUTTON_WPARAM(wParam) == XBUTTON1) ? 3 : 4; } if (bd->MouseButtonsDown == 0 && ::GetCapture() == nullptr) - ::SetCapture(hwnd); + ::SetCapture(hwnd); // Allow us to read mouse coordinates when dragging mouse outside of our window bounds. bd->MouseButtonsDown |= 1 << button; io.AddMouseSourceEvent(mouse_source); io.AddMouseButtonEvent(button, true); @@ -663,7 +670,7 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA case WM_MBUTTONUP: case WM_XBUTTONUP: { - ImGuiMouseSource mouse_source = GetMouseSourceFromMessageExtraInfo(); + ImGuiMouseSource mouse_source = ImGui_ImplWin32_GetMouseSourceFromMessageExtraInfo(); int button = 0; if (msg == WM_LBUTTONUP) { button = 0; } if (msg == WM_RBUTTONUP) { button = 1; } @@ -691,7 +698,7 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA if (wParam < 256) { // Submit modifiers - ImGui_ImplWin32_UpdateKeyModifiers(); + ImGui_ImplWin32_UpdateKeyModifiers(io); // Obtain virtual key code and convert to ImGuiKey const ImGuiKey key = ImGui_ImplWin32_KeyEventToImGuiKey(wParam, lParam); @@ -700,28 +707,28 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA // Special behavior for VK_SNAPSHOT / ImGuiKey_PrintScreen as Windows doesn't emit the key down event. if (key == ImGuiKey_PrintScreen && !is_key_down) - ImGui_ImplWin32_AddKeyEvent(key, true, vk, scancode); + ImGui_ImplWin32_AddKeyEvent(io, key, true, vk, scancode); // Submit key event if (key != ImGuiKey_None) - ImGui_ImplWin32_AddKeyEvent(key, is_key_down, vk, scancode); + ImGui_ImplWin32_AddKeyEvent(io, key, is_key_down, vk, scancode); // Submit individual left/right modifier events if (vk == VK_SHIFT) { // Important: Shift keys tend to get stuck when pressed together, missing key-up events are corrected in ImGui_ImplWin32_ProcessKeyEventsWorkarounds() - if (IsVkDown(VK_LSHIFT) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftShift, is_key_down, VK_LSHIFT, scancode); } - if (IsVkDown(VK_RSHIFT) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightShift, is_key_down, VK_RSHIFT, scancode); } + if (IsVkDown(VK_LSHIFT) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftShift, is_key_down, VK_LSHIFT, scancode); } + if (IsVkDown(VK_RSHIFT) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_RightShift, is_key_down, VK_RSHIFT, scancode); } } else if (vk == VK_CONTROL) { - if (IsVkDown(VK_LCONTROL) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftCtrl, is_key_down, VK_LCONTROL, scancode); } - if (IsVkDown(VK_RCONTROL) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightCtrl, is_key_down, VK_RCONTROL, scancode); } + if (IsVkDown(VK_LCONTROL) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftCtrl, is_key_down, VK_LCONTROL, scancode); } + if (IsVkDown(VK_RCONTROL) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_RightCtrl, is_key_down, VK_RCONTROL, scancode); } } else if (vk == VK_MENU) { - if (IsVkDown(VK_LMENU) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftAlt, is_key_down, VK_LMENU, scancode); } - if (IsVkDown(VK_RMENU) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightAlt, is_key_down, VK_RMENU, scancode); } + if (IsVkDown(VK_LMENU) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftAlt, is_key_down, VK_LMENU, scancode); } + if (IsVkDown(VK_RMENU) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_RightAlt, is_key_down, VK_RMENU, scancode); } } } return 0; @@ -731,7 +738,7 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA io.AddFocusEvent(msg == WM_SETFOCUS); return 0; case WM_INPUTLANGCHANGE: - ImGui_ImplWin32_UpdateKeyboardCodePage(); + ImGui_ImplWin32_UpdateKeyboardCodePage(io); return 0; case WM_CHAR: if (::IsWindowUnicode(hwnd)) @@ -749,7 +756,7 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA return 0; case WM_SETCURSOR: // This is required to restore cursor when transitioning from e.g resize borders to client area. - if (LOWORD(lParam) == HTCLIENT && ImGui_ImplWin32_UpdateMouseCursor()) + if (LOWORD(lParam) == HTCLIENT && ImGui_ImplWin32_UpdateMouseCursor(io, bd->LastMouseCursor)) return 1; return 0; case WM_DEVICECHANGE: diff --git a/Amalgam/include/ImGui/imgui_impl_win32.h b/Amalgam/include/ImGui/imgui_impl_win32.h index 3ad1a7ea..083fe385 100644 --- a/Amalgam/include/ImGui/imgui_impl_win32.h +++ b/Amalgam/include/ImGui/imgui_impl_win32.h @@ -4,9 +4,9 @@ // Implemented features: // [X] Platform: Clipboard support (for Win32 this is actually part of core dear imgui) // [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen. -// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy VK_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set] +// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy VK_* values are obsolete since 1.87 and not supported since 1.91.5] // [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. -// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. +// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. diff --git a/Amalgam/include/ImGui/imgui_internal.h b/Amalgam/include/ImGui/imgui_internal.h index c9ddceb3..af52d0fe 100644 --- a/Amalgam/include/ImGui/imgui_internal.h +++ b/Amalgam/include/ImGui/imgui_internal.h @@ -1,4 +1,4 @@ -// dear imgui, v1.91.4 +// dear imgui, v1.91.8 // (internal structures/api) // You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility. @@ -61,6 +61,14 @@ Index of this file: #if (defined __SSE__ || defined __x86_64__ || defined _M_X64 || (defined(_M_IX86_FP) && (_M_IX86_FP >= 1))) && !defined(IMGUI_DISABLE_SSE) #define IMGUI_ENABLE_SSE #include +#if (defined __AVX__ || defined __SSE4_2__) +#define IMGUI_ENABLE_SSE4_2 +#include +#endif +#endif +// Emscripten has partial SSE 4.2 support where _mm_crc32_u32 is not available. See https://emscripten.org/docs/porting/simd.html#id11 and #8213 +#if defined(IMGUI_ENABLE_SSE4_2) && !defined(IMGUI_USE_LEGACY_CRC32_ADLER) && !defined(__EMSCRIPTEN__) +#define IMGUI_ENABLE_SSE4_2_CRC #endif // Visual Studio warnings @@ -82,19 +90,19 @@ Index of this file: #endif #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' #pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants ok, for ImFloor() -#pragma clang diagnostic ignored "-Wunused-function" // for stb_textedit.h -#pragma clang diagnostic ignored "-Wmissing-prototypes" // for stb_textedit.h -#pragma clang diagnostic ignored "-Wold-style-cast" -#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" -#pragma clang diagnostic ignored "-Wdouble-promotion" +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wmissing-noreturn" // warning: function 'xxx' could be declared with attribute 'noreturn' #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access +#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type #elif defined(__GNUC__) #pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind -#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated #endif @@ -122,16 +130,24 @@ Index of this file: // [SECTION] Forward declarations //----------------------------------------------------------------------------- +// Utilities +// (other types which are not forwarded declared are: ImBitArray<>, ImSpan<>, ImSpanAllocator<>, ImPool<>, ImChunkStream<>) struct ImBitVector; // Store 1-bit per value struct ImRect; // An axis-aligned rectangle (2 points) +struct ImGuiTextIndex; // Maintain a line index for a text buffer. + +// ImDrawList/ImFontAtlas struct ImDrawDataBuilder; // Helper to build a ImDrawData instance struct ImDrawListSharedData; // Data shared between all ImDrawList instances + +// ImGui struct ImGuiBoxSelectState; // Box-selection state (currently used by multi-selection, could potentially be used by others) struct ImGuiColorMod; // Stacked color modifier, backup of modified data so we can restore it struct ImGuiContext; // Main Dear ImGui context struct ImGuiContextHook; // Hook for extensions like ImGuiTestEngine struct ImGuiDataVarInfo; // Variable information (e.g. to access style variables from an enum) struct ImGuiDataTypeInfo; // Type information associated to a ImGuiDataType enum +struct ImGuiDeactivatedItemData; // Data for IsItemDeactivated()/IsItemDeactivatedAfterEdit() function. struct ImGuiErrorRecoveryState; // Storage of stack sizes for error handling and recovery struct ImGuiGroupData; // Stacked storage data for BeginGroup()/EndGroup() struct ImGuiInputTextState; // Internal state of the currently focused/edited text input box @@ -177,6 +193,7 @@ typedef int ImGuiDebugLogFlags; // -> enum ImGuiDebugLogFlags_ // F typedef int ImGuiFocusRequestFlags; // -> enum ImGuiFocusRequestFlags_ // Flags: for FocusWindow() typedef int ImGuiItemStatusFlags; // -> enum ImGuiItemStatusFlags_ // Flags: for g.LastItemData.StatusFlags typedef int ImGuiOldColumnFlags; // -> enum ImGuiOldColumnFlags_ // Flags: for BeginColumns() +typedef int ImGuiLogFlags; // -> enum ImGuiLogFlags_ // Flags: for LogBegin() text capturing function typedef int ImGuiNavRenderCursorFlags; // -> enum ImGuiNavRenderCursorFlags_//Flags: for RenderNavCursor() typedef int ImGuiNavMoveFlags; // -> enum ImGuiNavMoveFlags_ // Flags: for navigation requests typedef int ImGuiNextItemDataFlags; // -> enum ImGuiNextItemDataFlags_ // Flags: for SetNextItemXXX() functions @@ -212,12 +229,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #endif // Debug Logging for ShowDebugLogWindow(). This is designed for relatively rare events so please don't spam. -#ifndef IMGUI_DISABLE_DEBUG_TOOLS -#define IMGUI_DEBUG_LOG(...) ImGui::DebugLog(__VA_ARGS__) -#else -#define IMGUI_DEBUG_LOG(...) ((void)0) -#endif -#define IMGUI_DEBUG_LOG_ERROR(...) do { ImGuiContext& g2 = *GImGui; if (g2.DebugLogFlags & ImGuiDebugLogFlags_EventError) IMGUI_DEBUG_LOG(__VA_ARGS__); else g2.DebugLogSkippedErrors++; } while (0) +#define IMGUI_DEBUG_LOG_ERROR(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventError) IMGUI_DEBUG_LOG(__VA_ARGS__); else g.DebugLogSkippedErrors++; } while (0) #define IMGUI_DEBUG_LOG_ACTIVEID(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventActiveId) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_FOCUS(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventFocus) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_POPUP(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventPopup) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) @@ -225,6 +237,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IMGUI_DEBUG_LOG_SELECTION(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_CLIPPER(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventClipper) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_IO(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventIO) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) +#define IMGUI_DEBUG_LOG_FONT(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventFont) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_INPUTROUTING(...) do{if (g.DebugLogFlags & ImGuiDebugLogFlags_EventInputRouting)IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) // Static Asserts @@ -260,6 +273,15 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IM_FLOOR IM_TRUNC #endif +// Hint for branch prediction +#if (defined(__cplusplus) && (__cplusplus >= 202002L)) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 202002L)) +#define IM_LIKELY [[likely]] +#define IM_UNLIKELY [[unlikely]] +#else +#define IM_LIKELY +#define IM_UNLIKELY +#endif + // Enforce cdecl calling convention for functions called by the standard library, in case compilation settings changed the default to e.g. __vectorcall #ifdef _MSC_VER #define IMGUI_CDECL __cdecl @@ -721,6 +743,7 @@ struct ImGuiTextIndex // Helper: ImGuiStorage IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStoragePair* in_end, ImGuiID key); + //----------------------------------------------------------------------------- // [SECTION] ImDrawList support //----------------------------------------------------------------------------- @@ -751,10 +774,13 @@ IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStorag #define IM_DRAWLIST_ARCFAST_SAMPLE_MAX IM_DRAWLIST_ARCFAST_TABLE_SIZE // Sample index _PathArcToFastEx() for 360 angle. // Data shared between all ImDrawList instances -// You may want to create your own instance of this if you want to use ImDrawList completely without ImGui. In that case, watch out for future changes to this structure. +// Conceptually this could have been called e.g. ImDrawListSharedContext +// Typically one ImGui context would create and maintain one of this. +// You may want to create your own instance of you try to ImDrawList completely without ImGui. In that case, watch out for future changes to this structure. struct IMGUI_API ImDrawListSharedData { ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas + const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas ImFont* Font; // Current/default font (optional, for simplified AddText overload) float FontSize; // Current/default font size (optional, for simplified AddText overload) float FontScale; // Current/default font scale (== FontSize / Font->FontSize) @@ -762,15 +788,12 @@ struct IMGUI_API ImDrawListSharedData float CircleSegmentMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) + ImVector TempBuffer; // Temporary write buffer - // [Internal] Temp write buffer - ImVector TempBuffer; - - // [Internal] Lookup tables + // Lookup tables ImVec2 ArcFastVtx[IM_DRAWLIST_ARCFAST_TABLE_SIZE]; // Sample points on the quarter of the circle. float ArcFastRadiusCutoff; // Cutoff radius after which arc drawing will fallback to slower PathArcTo() ImU8 CircleSegmentCounts[64]; // Precomputed segment count for given radius before we calculate it dynamically (to avoid calculation overhead) - const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas ImDrawListSharedData(); void SetCircleTessellationMaxError(float max_error); @@ -813,8 +836,7 @@ struct ImGuiDataTypeInfo // Extend ImGuiDataType_ enum ImGuiDataTypePrivate_ { - ImGuiDataType_String = ImGuiDataType_COUNT + 1, - ImGuiDataType_Pointer, + ImGuiDataType_Pointer = ImGuiDataType_COUNT + 1, ImGuiDataType_ID, }; @@ -900,7 +922,7 @@ enum ImGuiButtonFlagsPrivate_ ImGuiButtonFlags_PressedOnRelease = 1 << 7, // return true on release (default requires click+release) ImGuiButtonFlags_PressedOnDoubleClick = 1 << 8, // return true on double-click (default requires click+release) ImGuiButtonFlags_PressedOnDragDropHold = 1 << 9, // return true when held into while we are drag and dropping another item (used by e.g. tree nodes, collapsing headers) - //ImGuiButtonFlags_Repeat = 1 << 10, // hold to repeat + //ImGuiButtonFlags_Repeat = 1 << 10, // hold to repeat -> use ImGuiItemFlags_ButtonRepeat instead. ImGuiButtonFlags_FlattenChildren = 1 << 11, // allow interactions even if a child window is overlapping ImGuiButtonFlags_AllowOverlap = 1 << 12, // require previous frame HoveredId to either match id or be null before being usable. //ImGuiButtonFlags_DontClosePopups = 1 << 13, // disable automatically closing parent popup on press @@ -989,13 +1011,16 @@ enum ImGuiLayoutType_ ImGuiLayoutType_Vertical = 1 }; -enum ImGuiLogType +// Flags for LogBegin() text capturing function +enum ImGuiLogFlags_ { - ImGuiLogType_None = 0, - ImGuiLogType_TTY, - ImGuiLogType_File, - ImGuiLogType_Buffer, - ImGuiLogType_Clipboard, + ImGuiLogFlags_None = 0, + + ImGuiLogFlags_OutputTTY = 1 << 0, + ImGuiLogFlags_OutputFile = 1 << 1, + ImGuiLogFlags_OutputBuffer = 1 << 2, + ImGuiLogFlags_OutputClipboard = 1 << 3, + ImGuiLogFlags_OutputMask_ = ImGuiLogFlags_OutputTTY | ImGuiLogFlags_OutputFile | ImGuiLogFlags_OutputBuffer | ImGuiLogFlags_OutputClipboard, }; // X/Y enums are fixed to 0/1 so they may be used to index ImVec2 @@ -1054,7 +1079,7 @@ struct IMGUI_API ImGuiGroupData ImVec2 BackupCurrLineSize; float BackupCurrLineTextBaseOffset; ImGuiID BackupActiveIdIsAlive; - bool BackupActiveIdPreviousFrameIsAlive; + bool BackupDeactivatedIdIsAlive; bool BackupHoveredIdIsAlive; bool BackupIsSameLine; bool EmitItem; @@ -1105,26 +1130,27 @@ struct IMGUI_API ImGuiInputTextState { ImGuiContext* Ctx; // parent UI context (needs to be set explicitly by parent). ImStbTexteditState* Stb; // State for stb_textedit.h + ImGuiInputTextFlags Flags; // copy of InputText() flags. may be used to check if e.g. ImGuiInputTextFlags_Password is set. ImGuiID ID; // widget id owning the text state - int CurLenA; // UTF-8 length of the string in TextA (in bytes) - ImVector TextA; // main UTF8 buffer. - ImVector InitialTextA; // value to revert to when pressing Escape = backup of end-user buffer at the time of focus (in UTF-8, unaltered) + int TextLen; // UTF-8 length of the string in TextA (in bytes) + const char* TextSrc; // == TextA.Data unless read-only, in which case == buf passed to InputText(). Field only set and valid _inside_ the call InputText() call. + ImVector TextA; // main UTF8 buffer. TextA.Size is a buffer size! Should always be >= buf_size passed by user (and of course >= CurLenA + 1). + ImVector TextToRevertTo; // value to revert to when pressing Escape = backup of end-user buffer at the time of focus (in UTF-8, unaltered) ImVector CallbackTextBackup; // temporary storage for callback to support automatic reconcile of undo-stack - int BufCapacityA; // end-user buffer capacity + int BufCapacity; // end-user buffer capacity (include zero terminator) ImVec2 Scroll; // horizontal offset (managed manually) + vertical scrolling (pulled from child window's own Scroll.y) float CursorAnim; // timer for cursor blink, reset on every user action so the cursor reappears immediately bool CursorFollow; // set when we want scrolling to follow the current cursor position (not always!) bool SelectedAllMouseLock; // after a double-click to select all, we ignore further mouse drags to update selection bool Edited; // edited this frame - ImGuiInputTextFlags Flags; // copy of InputText() flags. may be used to check if e.g. ImGuiInputTextFlags_Password is set. - bool ReloadUserBuf; // force a reload of user buf so it may be modified externally. may be automatic in future version. - int ReloadSelectionStart; // POSITIONS ARE IN IMWCHAR units *NOT* UTF-8 this is why this is not exposed yet. + bool WantReloadUserBuf; // force a reload of user buf so it may be modified externally. may be automatic in future version. + int ReloadSelectionStart; int ReloadSelectionEnd; ImGuiInputTextState(); ~ImGuiInputTextState(); - void ClearText() { CurLenA = 0; TextA[0] = 0; CursorClamp(); } - void ClearFreeMemory() { TextA.clear(); InitialTextA.clear(); } + void ClearText() { TextLen = 0; TextA[0] = 0; CursorClamp(); } + void ClearFreeMemory() { TextA.clear(); TextToRevertTo.clear(); } void OnKeyPressed(int key); // Cannot be inline because we call in code in stb_textedit.h implementation void OnCharPressed(unsigned int c); @@ -1230,11 +1256,11 @@ struct ImGuiNextItemData struct ImGuiLastItemData { ImGuiID ID; - ImGuiItemFlags ItemFlags; // See ImGuiItemFlags_ + ImGuiItemFlags ItemFlags; // See ImGuiItemFlags_ (called 'InFlags' before v1.91.4). ImGuiItemStatusFlags StatusFlags; // See ImGuiItemStatusFlags_ ImRect Rect; // Full rectangle ImRect NavRect; // Navigation scoring rectangle (not displayed) - // Rarely used fields are not explicitly cleared, only valid when the corresponding ImGuiItemStatusFlags ar set. + // Rarely used fields are not explicitly cleared, only valid when the corresponding ImGuiItemStatusFlags are set. ImRect DisplayRect; // Display rectangle. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasDisplayRect) is set. ImRect ClipRect; // Clip rectangle at the time of submitting item. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasClipRect) is set.. ImGuiKeyChord Shortcut; // Shortcut at the time of submitting item. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasShortcut) is set.. @@ -1297,6 +1323,15 @@ struct ImGuiPtrOrIndex ImGuiPtrOrIndex(int index) { Ptr = NULL; Index = index; } }; +// Data used by IsItemDeactivated()/IsItemDeactivatedAfterEdit() functions +struct ImGuiDeactivatedItemData +{ + ImGuiID ID; + int ElapseFrame; + bool HasBeenEditedBefore; + bool IsAlive; +}; + //----------------------------------------------------------------------------- // [SECTION] Popup support //----------------------------------------------------------------------------- @@ -1907,6 +1942,7 @@ typedef void (*ImGuiErrorCallback)(ImGuiContext* ctx, void* user_data, const cha // [SECTION] Metrics, Debug Tools //----------------------------------------------------------------------------- +// See IMGUI_DEBUG_LOG() and IMGUI_DEBUG_LOG_XXX() macros. enum ImGuiDebugLogFlags_ { // Event types @@ -1919,11 +1955,12 @@ enum ImGuiDebugLogFlags_ ImGuiDebugLogFlags_EventClipper = 1 << 5, ImGuiDebugLogFlags_EventSelection = 1 << 6, ImGuiDebugLogFlags_EventIO = 1 << 7, - ImGuiDebugLogFlags_EventInputRouting = 1 << 8, - ImGuiDebugLogFlags_EventDocking = 1 << 9, // Unused in this branch - ImGuiDebugLogFlags_EventViewport = 1 << 10, // Unused in this branch + ImGuiDebugLogFlags_EventFont = 1 << 8, + ImGuiDebugLogFlags_EventInputRouting = 1 << 9, + ImGuiDebugLogFlags_EventDocking = 1 << 10, // Unused in this branch + ImGuiDebugLogFlags_EventViewport = 1 << 11, // Unused in this branch - ImGuiDebugLogFlags_EventMask_ = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus | ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav | ImGuiDebugLogFlags_EventClipper | ImGuiDebugLogFlags_EventSelection | ImGuiDebugLogFlags_EventIO | ImGuiDebugLogFlags_EventInputRouting | ImGuiDebugLogFlags_EventDocking | ImGuiDebugLogFlags_EventViewport, + ImGuiDebugLogFlags_EventMask_ = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus | ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav | ImGuiDebugLogFlags_EventClipper | ImGuiDebugLogFlags_EventSelection | ImGuiDebugLogFlags_EventIO | ImGuiDebugLogFlags_EventFont | ImGuiDebugLogFlags_EventInputRouting | ImGuiDebugLogFlags_EventDocking | ImGuiDebugLogFlags_EventViewport, ImGuiDebugLogFlags_OutputToTTY = 1 << 20, // Also send output to TTY ImGuiDebugLogFlags_OutputToTestEngine = 1 << 21, // Also send output to Test Engine }; @@ -2025,9 +2062,9 @@ struct ImGuiContext int FrameCount; int FrameCountEnded; int FrameCountRendered; + ImGuiID WithinEndChildID; // Set within EndChild() bool WithinFrameScope; // Set by NewFrame(), cleared by EndFrame() bool WithinFrameScopeWithImplicitWindow; // Set by NewFrame(), cleared by EndFrame() when the implicit debug window has been pushed - bool WithinEndChild; // Set within EndChild() bool GcCompactAll; // Request full GC bool TestEngineHookItems; // Will call test engine hooks: ImGuiTestEngineHook_ItemAdd(), ImGuiTestEngineHook_ItemInfo(), ImGuiTestEngineHook_Log() void* TestEngine; // Test engine user data @@ -2087,9 +2124,8 @@ struct ImGuiContext ImGuiWindow* ActiveIdWindow; ImGuiInputSource ActiveIdSource; // Activating source: ImGuiInputSource_Mouse OR ImGuiInputSource_Keyboard OR ImGuiInputSource_Gamepad ImGuiID ActiveIdPreviousFrame; - bool ActiveIdPreviousFrameIsAlive; - bool ActiveIdPreviousFrameHasBeenEditedBefore; - ImGuiWindow* ActiveIdPreviousFrameWindow; + ImGuiDeactivatedItemData DeactivatedItemData; + ImGuiDataTypeStorage ActiveIdValueOnActivation; // Backup of initial value at the time of activation. ONLY SET BY SPECIFIC WIDGETS: DragXXX and SliderXXX. ImGuiID LastActiveId; // Store the last non-zero ActiveId, useful for animation. float LastActiveIdTimer; // Store the last non-zero ActiveId timer since the beginning of activation, useful for animation. @@ -2316,7 +2352,8 @@ struct ImGuiContext // Capture/Logging bool LogEnabled; // Currently capturing - ImGuiLogType LogType; // Capture target + ImGuiLogFlags LogFlags; // Capture flags/type + ImGuiWindow* LogWindow; ImFileHandle LogFile; // If != NULL log to stdout/ file ImGuiTextBuffer LogBuffer; // Accumulation buffer when log to clipboard. This is pointer so our GImGui static constructor doesn't call heap allocators. const char* LogNextPrefix; @@ -2419,6 +2456,8 @@ struct IMGUI_API ImGuiWindowTempData ImGuiLayoutType LayoutType; ImGuiLayoutType ParentLayoutType; // Layout type of parent window at the time of Begin() ImU32 ModalDimBgColor; + ImGuiItemStatusFlags WindowItemStatusFlags; + ImGuiItemStatusFlags ChildItemStatusFlags; // Local parameters stacks // We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those settings. @@ -2514,6 +2553,8 @@ struct IMGUI_API ImGuiWindow ImGuiStorage StateStorage; ImVector ColumnsStorage; float FontWindowScale; // User scale multiplier per-window, via SetWindowFontScale() + float FontWindowScaleParents; + float FontRefSize; // This is a copy of window->CalcFontSize() at the time of Begin(), trying to phase out CalcFontSize() especially as it may be called on non-current window. int SettingsOffset; // Offset into SettingsWindows[] (offsets are always valid as we only grow the array from the back) ImDrawList* DrawList; // == &DrawListInst (for backward compatibility reason with code using imgui_internal.h we keep this a pointer) @@ -2548,7 +2589,7 @@ public: // We don't use g.FontSize because the window may be != g.CurrentWindow. ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } - float CalcFontSize() const { ImGuiContext& g = *Ctx; float scale = g.FontBaseSize * FontWindowScale; if (ParentWindow) scale *= ParentWindow->FontWindowScale; return scale; } + float CalcFontSize() const { ImGuiContext& g = *Ctx; return g.FontBaseSize * FontWindowScale * FontWindowScaleParents; } ImRect TitleBarRect() const { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight)); } ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight; return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight); } }; @@ -2571,6 +2612,8 @@ enum ImGuiTabItemFlagsPrivate_ ImGuiTabItemFlags_SectionMask_ = ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing, ImGuiTabItemFlags_NoCloseButton = 1 << 20, // Track whether p_open was set or not (we'll need this info on the next frame to recompute ContentWidth during layout) ImGuiTabItemFlags_Button = 1 << 21, // Used by TabItemButton, change the tab item behavior to mimic a button + ImGuiTabItemFlags_Invisible = 1 << 22, // To reserve space e.g. with ImGuiTabItemFlags_Leading + //ImGuiTabItemFlags_Unsorted = 1 << 23, // [Docking] Trailing tabs with the _Unsorted flag will be sorted based on the DockOrder of their Window. }; // Storage for one active tab item (sizeof() 40 bytes) @@ -2834,6 +2877,7 @@ struct IMGUI_API ImGuiTable ImGuiTableDrawChannelIdx DummyDrawChannel; // Redirect non-visible columns here. ImGuiTableDrawChannelIdx Bg2DrawChannelCurrent; // For Selectable() and other widgets drawing across columns after the freezing line. Index within DrawSplitter.Channels[] ImGuiTableDrawChannelIdx Bg2DrawChannelUnfrozen; + ImS8 NavLayer; // ImGuiNavLayer at the time of BeginTable(). bool IsLayoutLocked; // Set by TableUpdateLayout() which is called when beginning the first row. bool IsInsideRow; // Set when inside TableBeginRow()/TableEndRow(). bool IsInitializing; @@ -2936,6 +2980,7 @@ namespace ImGui // If this ever crashes because g.CurrentWindow is NULL, it means that either: // - ImGui::NewFrame() has never been called, which is illegal. // - You are calling ImGui functions after ImGui::EndFrame()/ImGui::Render() and before the next ImGui::NewFrame(), which is also illegal. + IMGUI_API ImGuiIO& GetIOEx(ImGuiContext* ctx); inline ImGuiWindow* GetCurrentWindowRead() { ImGuiContext& g = *GImGui; return g.CurrentWindow; } inline ImGuiWindow* GetCurrentWindow() { ImGuiContext& g = *GImGui; g.CurrentWindow->WriteAccessed = true; return g.CurrentWindow; } IMGUI_API ImGuiWindow* FindWindowByID(ImGuiID id); @@ -2974,6 +3019,7 @@ namespace ImGui // Fonts, drawing IMGUI_API void SetCurrentFont(ImFont* font); inline ImFont* GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; } + IMGUI_API void PushPasswordFont(); inline ImDrawList* GetForegroundDrawList(ImGuiWindow* window) { IM_UNUSED(window); return GetForegroundDrawList(); } // This seemingly unnecessary wrapper simplifies compatibility between the 'master' and 'docking' branches. IMGUI_API ImDrawList* GetBackgroundDrawList(ImGuiViewport* viewport); // get background draw list for the given viewport. this draw list will be the first rendering one. Useful to quickly draw shapes/text behind dear imgui contents. IMGUI_API ImDrawList* GetForegroundDrawList(ImGuiViewport* viewport); // get foreground draw list for the given viewport. this draw list will be the last rendered one. Useful to quickly draw shapes/text over dear imgui contents. @@ -2997,6 +3043,7 @@ namespace ImGui IMGUI_API void CallContextHooks(ImGuiContext* context, ImGuiContextHookType type); // Viewports + IMGUI_API void ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale); IMGUI_API void SetWindowViewport(ImGuiWindow* window, ImGuiViewportP* viewport); // Settings @@ -3054,7 +3101,7 @@ namespace ImGui IMGUI_API bool ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flags); IMGUI_API bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags = 0); IMGUI_API bool IsClippedEx(const ImRect& bb, ImGuiID id); - IMGUI_API void SetLastItemData(ImGuiID item_id, ImGuiItemFlags in_flags, ImGuiItemStatusFlags status_flags, const ImRect& item_rect); + IMGUI_API void SetLastItemData(ImGuiID item_id, ImGuiItemFlags item_flags, ImGuiItemStatusFlags status_flags, const ImRect& item_rect); IMGUI_API ImVec2 CalcItemSize(ImVec2 size, float default_w, float default_h); IMGUI_API float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x); IMGUI_API void PushMultiItemsWidths(int components, float width_full); @@ -3066,7 +3113,7 @@ namespace ImGui IMGUI_API void EndDisabledOverrideReenable(); // Logging/Capture - IMGUI_API void LogBegin(ImGuiLogType type, int auto_open_depth); // -> BeginCapture() when we design v2 api, for now stay under the radar by using the old name. + IMGUI_API void LogBegin(ImGuiLogFlags flags, int auto_open_depth); // -> BeginCapture() when we design v2 api, for now stay under the radar by using the old name. IMGUI_API void LogToBuffer(int auto_open_depth = -1); // Start logging/capturing to internal buffer IMGUI_API void LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL); IMGUI_API void LogSetNextTextDecoration(const char* prefix, const char* suffix); @@ -3335,6 +3382,7 @@ namespace ImGui IMGUI_API void TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, ImVec2 mouse_pos); IMGUI_API bool TabBarProcessReorder(ImGuiTabBar* tab_bar); IMGUI_API bool TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window); + IMGUI_API void TabItemSpacing(const char* str_id, ImGuiTabItemFlags flags, float width); IMGUI_API ImVec2 TabItemCalcSize(const char* label, bool has_close_button_or_unsaved_marker); IMGUI_API ImVec2 TabItemCalcSize(ImGuiWindow* window); IMGUI_API void TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col); @@ -3370,7 +3418,7 @@ namespace ImGui IMGUI_API void TextEx(const char* text, const char* text_end = NULL, ImGuiTextFlags flags = 0); IMGUI_API bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0, 0), ImGuiButtonFlags flags = 0); IMGUI_API bool ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, ImGuiButtonFlags flags = 0); - IMGUI_API bool ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags = 0); + IMGUI_API bool ImageButtonEx(ImGuiID id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags = 0); IMGUI_API void SeparatorEx(ImGuiSeparatorFlags flags, float thickness = 1.0f); IMGUI_API void SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_width); IMGUI_API bool CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value); @@ -3380,7 +3428,7 @@ namespace ImGui IMGUI_API bool CloseButton(ImGuiID id, const ImVec2& pos); IMGUI_API bool CollapseButton(ImGuiID id, const ImVec2& pos); IMGUI_API void Scrollbar(ImGuiAxis axis); - IMGUI_API bool ScrollbarEx(const ImRect& bb, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 avail_v, ImS64 contents_v, ImDrawFlags flags); + IMGUI_API bool ScrollbarEx(const ImRect& bb, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 avail_v, ImS64 contents_v, ImDrawFlags draw_rounding_flags = 0); IMGUI_API ImRect GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis); IMGUI_API ImGuiID GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis); IMGUI_API ImGuiID GetWindowResizeCornerID(ImGuiWindow* window, int n); // 0..3: corners @@ -3510,7 +3558,8 @@ namespace ImGui // [SECTION] ImFontAtlas internal API //----------------------------------------------------------------------------- -// This structure is likely to evolve as we add support for incremental atlas updates +// This structure is likely to evolve as we add support for incremental atlas updates. +// Conceptually this could be in ImGuiPlatformIO, but we are far from ready to make this public. struct ImFontBuilderIO { bool (*FontBuilder_Build)(ImFontAtlas* atlas); @@ -3529,6 +3578,7 @@ IMGUI_API void ImFontAtlasBuildRender8bppRectFromString(ImFontAtlas* atlas, IMGUI_API void ImFontAtlasBuildRender32bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned int in_marker_pixel_value); IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor); IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride); +IMGUI_API void ImFontAtlasBuildGetOversampleFactors(const ImFontConfig* cfg, int* out_oversample_h, int* out_oversample_v); //----------------------------------------------------------------------------- // [SECTION] Test Engine specific hooks (imgui_test_engine) diff --git a/Amalgam/include/ImGui/imgui_stdlib.cpp b/Amalgam/include/ImGui/imgui_stdlib.cpp index cf69aa89..c04d487c 100644 --- a/Amalgam/include/ImGui/imgui_stdlib.cpp +++ b/Amalgam/include/ImGui/imgui_stdlib.cpp @@ -8,6 +8,7 @@ // https://github.com/ocornut/imgui/wiki/Useful-Extensions#cness #include "imgui.h" +#ifndef IMGUI_DISABLE #include "imgui_stdlib.h" // Clang warnings with -Weverything @@ -83,3 +84,5 @@ bool ImGui::InputTextWithHint(const char* label, const char* hint, std::string* #if defined(__clang__) #pragma clang diagnostic pop #endif + +#endif // #ifndef IMGUI_DISABLE diff --git a/Amalgam/include/ImGui/imgui_stdlib.h b/Amalgam/include/ImGui/imgui_stdlib.h index 835a808f..697fc34a 100644 --- a/Amalgam/include/ImGui/imgui_stdlib.h +++ b/Amalgam/include/ImGui/imgui_stdlib.h @@ -9,6 +9,8 @@ #pragma once +#ifndef IMGUI_DISABLE + #include namespace ImGui @@ -19,3 +21,5 @@ namespace ImGui IMGUI_API bool InputTextMultiline(const char* label, std::string* str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* user_data = nullptr); IMGUI_API bool InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* user_data = nullptr); } + +#endif // #ifndef IMGUI_DISABLE diff --git a/Amalgam/include/ImGui/imgui_tables.cpp b/Amalgam/include/ImGui/imgui_tables.cpp index 598496be..e7a3b8e8 100644 --- a/Amalgam/include/ImGui/imgui_tables.cpp +++ b/Amalgam/include/ImGui/imgui_tables.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.91.4 +// dear imgui, v1.91.8 // (tables and columns code) /* @@ -229,9 +229,14 @@ Index of this file: #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access +#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type #elif defined(__GNUC__) #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'int'/'void*', but argument X has type 'unsigned int'/'ImGuiWindow*' +#pragma GCC diagnostic ignored "-Wstrict-overflow" #pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #endif @@ -369,6 +374,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->ColumnsCount = columns_count; table->IsLayoutLocked = false; table->InnerWidth = inner_width; + table->NavLayer = (ImS8)outer_window->DC.NavLayerCurrent; temp_data->UserOuterSize = outer_size; // Instance data (for instance 0, TableID == TableInstanceID) @@ -409,7 +415,8 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // Reset scroll if we are reactivating it if ((previous_flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) == 0) - SetNextWindowScroll(ImVec2(0.0f, 0.0f)); + if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasScroll) == 0) + SetNextWindowScroll(ImVec2(0.0f, 0.0f)); // Create scrolling region (without border and zero window padding) ImGuiWindowFlags child_window_flags = (flags & ImGuiTableFlags_ScrollX) ? ImGuiWindowFlags_HorizontalScrollbar : ImGuiWindowFlags_None; @@ -1044,7 +1051,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) const int column_n = table->DisplayOrderToIndex[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; - column->NavLayerCurrent = (ImS8)(table->FreezeRowsCount > 0 ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main); // Use Count NOT request so Header line changes layer when frozen + // Initial nav layer: using FreezeRowsCount, NOT FreezeRowsRequest, so Header line changes layer when frozen + column->NavLayerCurrent = (ImS8)(table->FreezeRowsCount > 0 ? ImGuiNavLayer_Menu : (ImGuiNavLayer)table->NavLayer); if (offset_x_frozen && table->FreezeColumnsCount == visible_n) { @@ -1331,7 +1339,11 @@ void ImGui::EndTable() { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL && "Only call EndTable() if BeginTable() returns true!"); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "EndTable() call should only be done while in BeginTable() scope!"); + return; + } // This assert would be very useful to catch a common error... unfortunately it would probably trigger in some // cases, and for consistency user may sometimes output empty tables (and still benefit from e.g. outer border) @@ -1483,7 +1495,7 @@ void ImGui::EndTable() if (inner_window != outer_window) { short backup_nav_layers_active_mask = inner_window->DC.NavLayersActiveMask; - inner_window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main; // So empty table don't appear to navigate differently. + inner_window->DC.NavLayersActiveMask |= 1 << table->NavLayer; // So empty table don't appear to navigate differently. g.CurrentTable = NULL; // To avoid error recovery recursing EndChild(); g.CurrentTable = table; @@ -1554,8 +1566,12 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!"); - IM_ASSERT(table->IsLayoutLocked == false && "Need to call call TableSetupColumn() before first row!"); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); + return; + } + IM_ASSERT(table->IsLayoutLocked == false && "Need to call TableSetupColumn() before first row!"); IM_ASSERT((flags & ImGuiTableColumnFlags_StatusMask_) == 0 && "Illegal to pass StatusMask values to TableSetupColumn()"); if (table->DeclColumnsCount >= table->ColumnsCount) { @@ -1628,7 +1644,11 @@ void ImGui::TableSetupScrollFreeze(int columns, int rows) { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!"); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); + return; + } IM_ASSERT(table->IsLayoutLocked == false && "Need to call TableSetupColumn() before first row!"); IM_ASSERT(columns >= 0 && columns < IMGUI_TABLE_MAX_COLUMNS); IM_ASSERT(rows >= 0 && rows < 128); // Arbitrary limit @@ -1705,9 +1725,11 @@ void ImGui::TableSetColumnEnabled(int column_n, bool enabled) { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL); - if (!table) + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); return; + } IM_ASSERT(table->Flags & ImGuiTableFlags_Hideable); // See comments above if (column_n < 0) column_n = table->CurrentColumn; @@ -2012,7 +2034,7 @@ void ImGui::TableEndRow(ImGuiTable* table) if (unfreeze_rows_request) { for (int column_n = 0; column_n < table->ColumnsCount; column_n++) - table->Columns[column_n].NavLayerCurrent = ImGuiNavLayer_Main; + table->Columns[column_n].NavLayerCurrent = table->NavLayer; const float y0 = ImMax(table->RowPosY2 + 1, table->InnerClipRect.Min.y); table_instance->LastFrozenHeight = y0 - table->OuterRect.Min.y; @@ -3028,7 +3050,11 @@ void ImGui::TableHeadersRow() { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL && "Need to call TableHeadersRow() after BeginTable()!"); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); + return; + } // Call layout if not already done. This is automatically done by TableNextRow: we do it here _only_ to make // it easier to debug-step in TableUpdateLayout(). Your own version of this function doesn't need this. @@ -3073,7 +3099,12 @@ void ImGui::TableHeader(const char* label) return; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL && "Need to call TableHeader() after BeginTable()!"); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); + return; + } + IM_ASSERT(table->CurrentColumn != -1); const int column_n = table->CurrentColumn; ImGuiTableColumn* column = &table->Columns[column_n]; @@ -3248,7 +3279,11 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label ImGuiTable* table = g.CurrentTable; ImGuiWindow* window = g.CurrentWindow; ImDrawList* draw_list = window->DrawList; - IM_ASSERT(table != NULL && "Need to call TableHeadersRow() after BeginTable()!"); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); + return; + } IM_ASSERT(table->CurrentRow == -1 && "Must be first row"); if (max_label_width == 0.0f) @@ -3293,7 +3328,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label const ImVec2 align = g.Style.TableAngledHeadersTextAlign; // Draw background and labels in first pass, then all borders. - float max_x = 0.0f; + float max_x = -FLT_MAX; for (int pass = 0; pass < 2; pass++) for (int order_n = 0; order_n < data_count; order_n++) { diff --git a/Amalgam/include/ImGui/imgui_widgets.cpp b/Amalgam/include/ImGui/imgui_widgets.cpp index 24040edf..387054de 100644 --- a/Amalgam/include/ImGui/imgui_widgets.cpp +++ b/Amalgam/include/ImGui/imgui_widgets.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.91.4 +// dear imgui, v1.91.8 // (widgets code) /* @@ -79,11 +79,17 @@ Index of this file: #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access +#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type #elif defined(__GNUC__) #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe +#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'int'/'void*', but argument X has type 'unsigned int'/'ImGuiWindow*' #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked -#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when simplifying division / ..when changing X +- C1 cmp C2 to X cmp C2 -+ C1 +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers #endif //------------------------------------------------------------------------- @@ -471,7 +477,7 @@ void ImGui::BulletTextV(const char* fmt, va_list args) // - PressedOnDragDropHold can generally be associated with any flag. // - PressedOnDoubleClick can be associated by PressedOnClickRelease/PressedOnRelease, in which case the second release event won't be reported. //------------------------------------------------------------------------------------------------------------------------------------------------ -// The behavior of the return-value changes when ImGuiButtonFlags_Repeat is set: +// The behavior of the return-value changes when ImGuiItemFlags_ButtonRepeat is set: // Repeat+ Repeat+ Repeat+ Repeat+ // PressedOnClickRelease PressedOnClick PressedOnRelease PressedOnDoubleClick //------------------------------------------------------------------------------------------------------------------------------------------------- @@ -495,19 +501,19 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); + // Default behavior inherited from item flags + // Note that _both_ ButtonFlags and ItemFlags are valid sources, so copy one into the item_flags and only check that. + ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.ItemFlags : g.CurrentItemFlags); + if (flags & ImGuiButtonFlags_AllowOverlap) + item_flags |= ImGuiItemFlags_AllowOverlap; + // Default only reacts to left mouse button if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0) flags |= ImGuiButtonFlags_MouseButtonLeft; // Default behavior requires click + release inside bounding box if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0) - flags |= ImGuiButtonFlags_PressedOnDefault_; - - // Default behavior inherited from item flags - // Note that _both_ ButtonFlags and ItemFlags are valid sources, so copy one into the item_flags and only check that. - ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.ItemFlags : g.CurrentItemFlags); - if (flags & ImGuiButtonFlags_AllowOverlap) - item_flags |= ImGuiItemFlags_AllowOverlap; + flags |= (item_flags & ImGuiItemFlags_ButtonRepeat) ? ImGuiButtonFlags_PressedOnClick : ImGuiButtonFlags_PressedOnDefault_; ImGuiWindow* backup_hovered_window = g.HoveredWindow; const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindow == window; @@ -903,15 +909,17 @@ ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis) // Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set. ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis) { + ImGuiContext& g = *GImGui; const ImRect outer_rect = window->Rect(); const ImRect inner_rect = window->InnerRect; - const float border_size = window->WindowBorderSize; const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar) IM_ASSERT(scrollbar_size > 0.0f); + const float border_size = IM_ROUND(window->WindowBorderSize * 0.5f); + const float border_top = (window->Flags & ImGuiWindowFlags_MenuBar) ? IM_ROUND(g.Style.FrameBorderSize * 0.5f) : 0.0f; if (axis == ImGuiAxis_X) - return ImRect(inner_rect.Min.x, ImMax(outer_rect.Min.y, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x - border_size, outer_rect.Max.y - border_size); + return ImRect(inner_rect.Min.x + border_size, ImMax(outer_rect.Min.y + border_size, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x - border_size, outer_rect.Max.y - border_size); else - return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y, outer_rect.Max.x - border_size, inner_rect.Max.y - border_size); + return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y + border_top, outer_rect.Max.x - border_size, inner_rect.Max.y - border_size); } void ImGui::Scrollbar(ImGuiAxis axis) @@ -949,7 +957,7 @@ void ImGui::Scrollbar(ImGuiAxis axis) // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal. // Still, the code should probably be made simpler.. -bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_visible_v, ImS64 size_contents_v, ImDrawFlags flags) +bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_visible_v, ImS64 size_contents_v, ImDrawFlags draw_rounding_flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; @@ -1040,7 +1048,7 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 // Render const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg); const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha); - window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, flags); + window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, draw_rounding_flags); ImRect grab_rect; if (axis == ImGuiAxis_X) grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, bb.Max.y); @@ -1072,9 +1080,7 @@ void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const I window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); } -// ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390) -// We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API. -bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) +bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); @@ -1096,12 +1102,13 @@ bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& imag RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding)); if (bg_col.w > 0.0f) window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col)); - window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); + window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); return pressed; } -// Note that ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button. +// - ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button. +// - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. (#8165) // FIXME: Maybe that's not the best design? bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) { ImGuiContext& g = *GImGui; @@ -1114,7 +1121,7 @@ bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const I #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Legacy API obsoleted in 1.89. Two differences with new ImageButton() -// - old ImageButton() used ImTextureId as item id (created issue with multiple buttons with same image, transient texture id values, opaque computation of ID) +// - old ImageButton() used ImTextureID as item id (created issue with multiple buttons with same image, transient texture id values, opaque computation of ID) // - new ImageButton() requires an explicit 'const char* str_id' // - old ImageButton() had frame_padding' override argument. // - new ImageButton() always use style.FramePadding. @@ -2166,6 +2173,7 @@ static const ImGuiDataTypeInfo GDataTypeInfo[] = { sizeof(float), "float", "%.3f","%f" }, // ImGuiDataType_Float (float are promoted to double in va_arg) { sizeof(double), "double","%f", "%lf" }, // ImGuiDataType_Double { sizeof(bool), "bool", "%d", "%d" }, // ImGuiDataType_Bool + { 0, "char*","%s", "%s" }, // ImGuiDataType_String }; IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT); @@ -2434,9 +2442,9 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) { adjust_delta = g.IO.MouseDelta[axis]; - if (g.IO.KeyAlt) + if (g.IO.KeyAlt && !(flags & ImGuiSliderFlags_NoSpeedTweaks)) adjust_delta *= 1.0f / 100.0f; - if (g.IO.KeyShift) + if (g.IO.KeyShift && !(flags & ImGuiSliderFlags_NoSpeedTweaks)) adjust_delta *= 10.0f; } else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) @@ -2444,7 +2452,7 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0; const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow); const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast); - const float tweak_factor = tweak_slow ? 1.0f / 10.0f : tweak_fast ? 10.0f : 1.0f; + const float tweak_factor = (flags & ImGuiSliderFlags_NoSpeedTweaks) ? 1.0f : tweak_slow ? 1.0f / 10.0f : tweak_fast ? 10.0f : 1.0f; adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor; v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision)); } @@ -2632,6 +2640,10 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, temp_input_is_active = true; } + // Store initial value (not used by main lib but available as a convenience but some mods e.g. to revert) + if (make_active) + memcpy(&g.ActiveIdValueOnActivation, p_data, DataTypeGetInfo(data_type)->Size); + if (make_active && !temp_input_is_active) { SetActiveID(id, window); @@ -3222,6 +3234,10 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat if ((clicked && g.IO.KeyCtrl) || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput))) temp_input_is_active = true; + // Store initial value (not used by main lib but available as a convenience but some mods e.g. to revert) + if (make_active) + memcpy(&g.ActiveIdValueOnActivation, p_data, DataTypeGetInfo(data_type)->Size); + if (make_active && !temp_input_is_active) { SetActiveID(id, window); @@ -3329,7 +3345,8 @@ bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, fl format = "%.0f deg"; float v_deg = (*v_rad) * 360.0f / (2 * IM_PI); bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, flags); - *v_rad = v_deg * (2 * IM_PI) / 360.0f; + if (value_changed) + *v_rad = v_deg * (2 * IM_PI) / 360.0f; return value_changed; } @@ -3928,15 +3945,15 @@ static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, c // - ...but we don't use that feature. namespace ImStb { -static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->CurLenA; } -static char STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx <= obj->CurLenA); return obj->TextA[idx]; } -static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextA.Data + line_start_idx + char_idx, obj->TextA.Data + obj->TextA.Size); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance((ImWchar)c) * g.FontScale; } +static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->TextLen; } +static char STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx <= obj->TextLen); return obj->TextSrc[idx]; } +static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextSrc + line_start_idx + char_idx, obj->TextSrc + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance((ImWchar)c) * g.FontScale; } static char STB_TEXTEDIT_NEWLINE = '\n'; static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx) { - const char* text = obj->TextA.Data; + const char* text = obj->TextSrc; const char* text_remaining = NULL; - const ImVec2 size = InputTextCalcTextSize(obj->Ctx, text + line_start_idx, text + obj->CurLenA, &text_remaining, NULL, true); + const ImVec2 size = InputTextCalcTextSize(obj->Ctx, text + line_start_idx, text + obj->TextLen, &text_remaining, NULL, true); r->x0 = 0.0f; r->x1 = size.x; r->baseline_y_delta = size.y; @@ -3950,18 +3967,18 @@ static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* ob static int IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx) { - if (idx >= obj->CurLenA) - return obj->CurLenA + 1; + if (idx >= obj->TextLen) + return obj->TextLen + 1; unsigned int c; - return idx + ImTextCharFromUtf8(&c, obj->TextA.Data + idx, obj->TextA.Data + obj->TextA.Size); + return idx + ImTextCharFromUtf8(&c, obj->TextSrc + idx, obj->TextSrc + obj->TextLen); } static int IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx) { if (idx <= 0) return -1; - const char* p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, obj->TextA.Data + idx); - return (int)(p - obj->TextA.Data); + const char* p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, obj->TextSrc + idx); + return (int)(p - obj->TextSrc); } static bool ImCharIsSeparatorW(unsigned int c) @@ -3984,10 +4001,10 @@ static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx) if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0) return 0; - const char* curr_p = obj->TextA.Data + idx; - const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, curr_p); - unsigned int curr_c; ImTextCharFromUtf8(&curr_c, curr_p, obj->TextA.Data + obj->TextA.Size); - unsigned int prev_c; ImTextCharFromUtf8(&prev_c, prev_p, obj->TextA.Data + obj->TextA.Size); + const char* curr_p = obj->TextSrc + idx; + const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p); + unsigned int curr_c; ImTextCharFromUtf8(&curr_c, curr_p, obj->TextSrc + obj->TextLen); + unsigned int prev_c; ImTextCharFromUtf8(&prev_c, prev_p, obj->TextSrc + obj->TextLen); bool prev_white = ImCharIsBlankW(prev_c); bool prev_separ = ImCharIsSeparatorW(prev_c); @@ -4000,10 +4017,10 @@ static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx) if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0) return 0; - const char* curr_p = obj->TextA.Data + idx; - const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, curr_p); - unsigned int prev_c; ImTextCharFromUtf8(&prev_c, curr_p, obj->TextA.Data + obj->TextA.Size); - unsigned int curr_c; ImTextCharFromUtf8(&curr_c, prev_p, obj->TextA.Data + obj->TextA.Size); + const char* curr_p = obj->TextSrc + idx; + const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p); + unsigned int prev_c; ImTextCharFromUtf8(&prev_c, curr_p, obj->TextSrc + obj->TextLen); + unsigned int curr_c; ImTextCharFromUtf8(&curr_c, prev_p, obj->TextSrc + obj->TextLen); bool prev_white = ImCharIsBlankW(prev_c); bool prev_separ = ImCharIsSeparatorW(prev_c); @@ -4020,7 +4037,7 @@ static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx) } static int STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ImGuiInputTextState* obj, int idx) { - int len = obj->CurLenA; + int len = obj->TextLen; idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx); while (idx < len && !is_word_boundary_from_left(obj, idx)) idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx); @@ -4029,7 +4046,7 @@ static int STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ImGuiInputTextState* obj, int idx) static int STB_TEXTEDIT_MOVEWORDRIGHT_WIN(ImGuiInputTextState* obj, int idx) { idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx); - int len = obj->CurLenA; + int len = obj->TextLen; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx); return idx > len ? len : idx; @@ -4040,33 +4057,32 @@ static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx) static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n) { + // Offset remaining text (+ copy zero terminator) + IM_ASSERT(obj->TextSrc == obj->TextA.Data); char* dst = obj->TextA.Data + pos; - + char* src = obj->TextA.Data + pos + n; + memmove(dst, src, obj->TextLen - n - pos + 1); obj->Edited = true; - obj->CurLenA -= n; - - // Offset remaining text (FIXME-OPT: Use memmove) - const char* src = obj->TextA.Data + pos + n; - while (char c = *src++) - *dst++ = c; - *dst = '\0'; + obj->TextLen -= n; } static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const char* new_text, int new_text_len) { const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0; - const int text_len = obj->CurLenA; + const int text_len = obj->TextLen; IM_ASSERT(pos <= text_len); - if (!is_resizable && (new_text_len + obj->CurLenA + 1 > obj->BufCapacityA)) + if (!is_resizable && (new_text_len + obj->TextLen + 1 > obj->BufCapacity)) return false; // Grow internal buffer if needed + IM_ASSERT(obj->TextSrc == obj->TextA.Data); if (new_text_len + text_len + 1 > obj->TextA.Size) { if (!is_resizable) return false; obj->TextA.resize(text_len + ImClamp(new_text_len, 32, ImMax(256, new_text_len)) + 1); + obj->TextSrc = obj->TextA.Data; } char* text = obj->TextA.Data; @@ -4075,8 +4091,8 @@ static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const ch memcpy(text + pos, new_text, (size_t)new_text_len); obj->Edited = true; - obj->CurLenA += new_text_len; - obj->TextA[obj->CurLenA] = '\0'; + obj->TextLen += new_text_len; + obj->TextA[obj->TextLen] = '\0'; return true; } @@ -4108,8 +4124,8 @@ static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const ch // the stb_textedit_paste() function creates two separate records, so we perform it manually. (FIXME: Report to nothings/stb?) static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len) { - stb_text_makeundo_replace(str, state, 0, str->CurLenA, text_len); - ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->CurLenA); + stb_text_makeundo_replace(str, state, 0, str->TextLen, text_len); + ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->TextLen); state->cursor = state->select_start = state->select_end = 0; if (text_len <= 0) return; @@ -4157,33 +4173,32 @@ void ImGuiInputTextState::OnCharPressed(unsigned int c) // Those functions are not inlined in imgui_internal.h, allowing us to hide ImStbTexteditState from that header. void ImGuiInputTextState::CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking -void ImGuiInputTextState::CursorClamp() { Stb->cursor = ImMin(Stb->cursor, CurLenA); Stb->select_start = ImMin(Stb->select_start, CurLenA); Stb->select_end = ImMin(Stb->select_end, CurLenA); } +void ImGuiInputTextState::CursorClamp() { Stb->cursor = ImMin(Stb->cursor, TextLen); Stb->select_start = ImMin(Stb->select_start, TextLen); Stb->select_end = ImMin(Stb->select_end, TextLen); } bool ImGuiInputTextState::HasSelection() const { return Stb->select_start != Stb->select_end; } void ImGuiInputTextState::ClearSelection() { Stb->select_start = Stb->select_end = Stb->cursor; } int ImGuiInputTextState::GetCursorPos() const { return Stb->cursor; } int ImGuiInputTextState::GetSelectionStart() const { return Stb->select_start; } int ImGuiInputTextState::GetSelectionEnd() const { return Stb->select_end; } -void ImGuiInputTextState::SelectAll() { Stb->select_start = 0; Stb->cursor = Stb->select_end = CurLenA; Stb->has_preferred_x = 0; } -void ImGuiInputTextState::ReloadUserBufAndSelectAll() { ReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; } -void ImGuiInputTextState::ReloadUserBufAndKeepSelection() { ReloadUserBuf = true; ReloadSelectionStart = Stb->select_start; ReloadSelectionEnd = Stb->select_end; } -void ImGuiInputTextState::ReloadUserBufAndMoveToEnd() { ReloadUserBuf = true; ReloadSelectionStart = ReloadSelectionEnd = INT_MAX; } +void ImGuiInputTextState::SelectAll() { Stb->select_start = 0; Stb->cursor = Stb->select_end = TextLen; Stb->has_preferred_x = 0; } +void ImGuiInputTextState::ReloadUserBufAndSelectAll() { WantReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; } +void ImGuiInputTextState::ReloadUserBufAndKeepSelection() { WantReloadUserBuf = true; ReloadSelectionStart = Stb->select_start; ReloadSelectionEnd = Stb->select_end; } +void ImGuiInputTextState::ReloadUserBufAndMoveToEnd() { WantReloadUserBuf = true; ReloadSelectionStart = ReloadSelectionEnd = INT_MAX; } ImGuiInputTextCallbackData::ImGuiInputTextCallbackData() { memset(this, 0, sizeof(*this)); } -// Public API to manipulate UTF-8 text -// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar) +// Public API to manipulate UTF-8 text from within a callback. // FIXME: The existence of this rarely exercised code path is a bit of a nuisance. +// Historically they existed because STB_TEXTEDIT_INSERTCHARS() etc. worked on our ImWchar +// buffer, but nowadays they both work on UTF-8 data. Should aim to merge both. void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count) { IM_ASSERT(pos + bytes_count <= BufTextLen); char* dst = Buf + pos; const char* src = Buf + pos + bytes_count; - while (char c = *src++) - *dst++ = c; - *dst = '\0'; + memmove(dst, src, BufTextLen - bytes_count - pos + 1); if (CursorPos >= pos + bytes_count) CursorPos -= bytes_count; @@ -4200,6 +4215,7 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons if (new_text == new_text_end) return; + // Grow internal buffer if needed const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0; const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text); if (new_text_len + BufTextLen >= BufSize) @@ -4207,15 +4223,15 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons if (!is_resizable) return; - // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the mildly similar code (until we remove the U16 buffer altogether!) ImGuiContext& g = *Ctx; ImGuiInputTextState* edit_state = &g.InputTextState; IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID); IM_ASSERT(Buf == edit_state->TextA.Data); int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1; - edit_state->TextA.reserve(new_buf_size + 1); + edit_state->TextA.resize(new_buf_size + 1); + edit_state->TextSrc = edit_state->TextA.Data; Buf = edit_state->TextA.Data; - BufSize = edit_state->BufCapacityA = new_buf_size; + BufSize = edit_state->BufCapacity = new_buf_size; } if (BufTextLen != pos) @@ -4230,6 +4246,23 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons BufTextLen += new_text_len; } +void ImGui::PushPasswordFont() +{ + ImGuiContext& g = *GImGui; + ImFont* in_font = g.Font; + ImFont* out_font = &g.InputTextPasswordFont; + const ImFontGlyph* glyph = in_font->FindGlyph('*'); + out_font->FontSize = in_font->FontSize; + out_font->Scale = in_font->Scale; + out_font->Ascent = in_font->Ascent; + out_font->Descent = in_font->Descent; + out_font->ContainerAtlas = in_font->ContainerAtlas; + out_font->FallbackGlyph = glyph; + out_font->FallbackAdvanceX = glyph->AdvanceX; + IM_ASSERT(out_font->Glyphs.Size == 0 && out_font->IndexAdvanceX.Size == 0 && out_font->IndexLookup.Size == 0); + PushFont(out_font); +} + // Return false to discard a character. static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard) { @@ -4331,26 +4364,23 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im return true; } -// Find the shortest single replacement we can make to get the new text from the old text. -// Important: needs to be run before TextW is rewritten with the new characters because calling STB_TEXTEDIT_GETCHAR() at the end. +// Find the shortest single replacement we can make to get from old_buf to new_buf +// Note that this doesn't directly alter state->TextA, state->TextLen. They are expected to be made valid separately. // FIXME: Ideally we should transition toward (1) making InsertChars()/DeleteChars() update undo-stack (2) discourage (and keep reconcile) or obsolete (and remove reconcile) accessing buffer directly. -static void InputTextReconcileUndoStateAfterUserCallback(ImGuiInputTextState* state, const char* new_buf_a, int new_length_a) +static void InputTextReconcileUndoState(ImGuiInputTextState* state, const char* old_buf, int old_length, const char* new_buf, int new_length) { - const char* old_buf = state->CallbackTextBackup.Data; - const int old_length = state->CallbackTextBackup.Size - 1; - - const int shorter_length = ImMin(old_length, new_length_a); + const int shorter_length = ImMin(old_length, new_length); int first_diff; for (first_diff = 0; first_diff < shorter_length; first_diff++) - if (old_buf[first_diff] != new_buf_a[first_diff]) + if (old_buf[first_diff] != new_buf[first_diff]) break; - if (first_diff == old_length && first_diff == new_length_a) + if (first_diff == old_length && first_diff == new_length) return; int old_last_diff = old_length - 1; - int new_last_diff = new_length_a - 1; + int new_last_diff = new_length - 1; for (; old_last_diff >= first_diff && new_last_diff >= first_diff; old_last_diff--, new_last_diff--) - if (old_buf[old_last_diff] != new_buf_a[new_last_diff]) + if (old_buf[old_last_diff] != new_buf[new_last_diff]) break; const int insert_len = new_last_diff - first_diff + 1; @@ -4379,8 +4409,9 @@ void ImGui::InputTextDeactivateHook(ImGuiID id) else { IM_ASSERT(state->TextA.Data != 0); - g.InputTextDeactivatedState.TextA.resize(state->CurLenA + 1); - memcpy(g.InputTextDeactivatedState.TextA.Data, state->TextA.Data, state->CurLenA + 1); + IM_ASSERT(state->TextA[state->TextLen] == 0); + g.InputTextDeactivatedState.TextA.resize(state->TextLen + 1); + memcpy(g.InputTextDeactivatedState.TextA.Data, state->TextA.Data, state->TextLen + 1); } } @@ -4401,6 +4432,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ IM_ASSERT(buf != NULL && buf_size >= 0); IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys) IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key) + IM_ASSERT(!((flags & ImGuiInputTextFlags_ElideLeft) && (flags & ImGuiInputTextFlags_Multiline))); // Multiline will not work with left-trimming ImGuiContext& g = *GImGui; ImGuiIO& io = g.IO; @@ -4501,60 +4533,67 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX; - const bool init_reload_from_user_buf = (state != NULL && state->ReloadUserBuf); + const bool init_reload_from_user_buf = (state != NULL && state->WantReloadUserBuf); const bool init_changed_specs = (state != NULL && state->Stb->single_line != !is_multiline); // state != NULL means its our state. const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav); const bool init_state = (init_make_active || user_scroll_active); - if ((init_state && g.ActiveId != id) || init_changed_specs || init_reload_from_user_buf) + if (init_reload_from_user_buf) + { + int new_len = (int)strlen(buf); + IM_ASSERT(new_len + 1 <= buf_size && "Is your input buffer properly zero-terminated?"); + state->WantReloadUserBuf = false; + InputTextReconcileUndoState(state, state->TextA.Data, state->TextLen, buf, new_len); + state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string. + state->TextLen = new_len; + memcpy(state->TextA.Data, buf, state->TextLen + 1); + state->Stb->select_start = state->ReloadSelectionStart; + state->Stb->cursor = state->Stb->select_end = state->ReloadSelectionEnd; + state->CursorClamp(); + } + else if ((init_state && g.ActiveId != id) || init_changed_specs) { // Access state even if we don't own it yet. state = &g.InputTextState; state->CursorAnimReset(); - state->ReloadUserBuf = false; // Backup state of deactivating item so they'll have a chance to do a write to output buffer on the same frame they report IsItemDeactivatedAfterEdit (#4714) InputTextDeactivateHook(state->ID); + // Take a copy of the initial buffer value. // From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode) const int buf_len = (int)strlen(buf); - if (!init_reload_from_user_buf) - { - // Take a copy of the initial buffer value. - state->InitialTextA.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string. - memcpy(state->InitialTextA.Data, buf, buf_len + 1); - } + IM_ASSERT(buf_len + 1 <= buf_size && "Is your input buffer properly zero-terminated?"); + state->TextToRevertTo.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string. + memcpy(state->TextToRevertTo.Data, buf, buf_len + 1); // Preserve cursor position and undo/redo stack if we come back to same widget // FIXME: Since we reworked this on 2022/06, may want to differentiate recycle_cursor vs recycle_undostate? - bool recycle_state = (state->ID == id && !init_changed_specs && !init_reload_from_user_buf); - if (recycle_state && (state->CurLenA != buf_len || (strncmp(state->TextA.Data, buf, buf_len) != 0))) + bool recycle_state = (state->ID == id && !init_changed_specs); + if (recycle_state && (state->TextLen != buf_len || (state->TextA.Data == NULL || strncmp(state->TextA.Data, buf, buf_len) != 0))) recycle_state = false; // Start edition state->ID = id; - state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string. - state->CurLenA = (int)strlen(buf); - memcpy(state->TextA.Data, buf, state->CurLenA + 1); + state->TextLen = buf_len; + if (!is_readonly) + { + state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string. + memcpy(state->TextA.Data, buf, state->TextLen + 1); + } + // Find initial scroll position for right alignment + state->Scroll = ImVec2(0.0f, 0.0f); + if (flags & ImGuiInputTextFlags_ElideLeft) + state->Scroll.x += ImMax(0.0f, CalcTextSize(buf).x - frame_size.x + style.FramePadding.x * 2.0f); + + // Recycle existing cursor/selection/undo stack but clamp position + // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler. if (recycle_state) - { - // Recycle existing cursor/selection/undo stack but clamp position - // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler. state->CursorClamp(); - } else - { - state->Scroll = ImVec2(0.0f, 0.0f); stb_textedit_initialize_state(state->Stb, !is_multiline); - } - if (init_reload_from_user_buf) - { - state->Stb->select_start = state->ReloadSelectionStart; - state->Stb->cursor = state->Stb->select_end = state->ReloadSelectionEnd; - state->CursorClamp(); - } - else if (!is_multiline) + if (!is_multiline) { if (flags & ImGuiInputTextFlags_AutoSelectAll) select_all = true; @@ -4604,7 +4643,15 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Expose scroll in a manner that is agnostic to us using a child window if (is_multiline && state != NULL) state->Scroll.y = draw_window->Scroll.y; + + // Read-only mode always ever read from source buffer. Refresh TextLen when active. + if (is_readonly && state != NULL) + state->TextLen = (int)strlen(buf); + //if (is_readonly && state != NULL) + // state->TextA.clear(); // Uncomment to facilitate debugging, but we otherwise prefer to keep/amortize th allocation. } + if (state != NULL) + state->TextSrc = is_readonly ? buf : state->TextA.Data; // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function) if (g.ActiveId == id && state == NULL) @@ -4626,26 +4673,14 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Password pushes a temporary font with only a fallback glyph if (is_password && !is_displaying_hint) - { - const ImFontGlyph* glyph = g.Font->FindGlyph('*'); - ImFont* password_font = &g.InputTextPasswordFont; - password_font->FontSize = g.Font->FontSize; - password_font->Scale = g.Font->Scale; - password_font->Ascent = g.Font->Ascent; - password_font->Descent = g.Font->Descent; - password_font->ContainerAtlas = g.Font->ContainerAtlas; - password_font->FallbackGlyph = glyph; - password_font->FallbackAdvanceX = glyph->AdvanceX; - IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty()); - PushFont(password_font); - } + PushPasswordFont(); // Process mouse inputs and character inputs if (g.ActiveId == id) { IM_ASSERT(state != NULL); state->Edited = false; - state->BufCapacityA = buf_size; + state->BufCapacity = buf_size; state->Flags = flags; // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. @@ -4671,10 +4706,10 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // We always use the "Mac" word advance for double-click select vs CTRL+Right which use the platform dependent variant: // FIXME: There are likely many ways to improve this behavior, but there's no "right" behavior (depends on use-case, software, OS) const bool is_bol = (state->Stb->cursor == 0) || ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor - 1) == '\n'; - if (((state->Stb)->select_start != (state->Stb)->select_end) || !is_bol) + if (STB_TEXT_HAS_SELECTION(state->Stb) || !is_bol) state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); //state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); - if (!((state->Stb)->select_start != (state->Stb)->select_end)) + if (!STB_TEXT_HAS_SELECTION(state->Stb)) ImStb::stb_textedit_prep_selection_at_cursor(state->Stb); state->Stb->cursor = ImStb::STB_TEXTEDIT_MOVEWORDRIGHT_MAC(state, state->Stb->cursor); state->Stb->select_end = state->Stb->cursor; @@ -4869,13 +4904,13 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Cut, Copy if (g.PlatformIO.Platform_SetClipboardTextFn != NULL) { + // SetClipboardText() only takes null terminated strings + state->TextSrc may point to read-only user buffer, so we need to make a copy. const int ib = state->HasSelection() ? ImMin(state->Stb->select_start, state->Stb->select_end) : 0; - const int ie = state->HasSelection() ? ImMax(state->Stb->select_start, state->Stb->select_end) : state->CurLenA; - - char backup = state->TextA.Data[ie]; - state->TextA.Data[ie] = 0; // A bit of a hack since SetClipboardText only takes null terminated strings - SetClipboardText(state->TextA.Data + ib); - state->TextA.Data[ie] = backup; + const int ie = state->HasSelection() ? ImMax(state->Stb->select_start, state->Stb->select_end) : state->TextLen; + g.TempBuffer.reserve(ie - ib + 1); + memcpy(g.TempBuffer.Data, state->TextSrc + ib, ie - ib); + g.TempBuffer.Data[ie - ib] = 0; + SetClipboardText(g.TempBuffer.Data); } if (is_cut) { @@ -4891,25 +4926,27 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { // Filter pasted buffer const int clipboard_len = (int)strlen(clipboard); - char* clipboard_filtered = (char*)IM_ALLOC(clipboard_len + 1); - int clipboard_filtered_len = 0; + ImVector clipboard_filtered; + clipboard_filtered.reserve(clipboard_len + 1); for (const char* s = clipboard; *s != 0; ) { unsigned int c; - int len = ImTextCharFromUtf8(&c, s, NULL); - s += len; + int in_len = ImTextCharFromUtf8(&c, s, NULL); + s += in_len; if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, true)) continue; - memcpy(clipboard_filtered + clipboard_filtered_len, s - len, len); - clipboard_filtered_len += len; + char c_utf8[5]; + ImTextCharToUtf8(c_utf8, c); + int out_len = (int)strlen(c_utf8); + clipboard_filtered.resize(clipboard_filtered.Size + out_len); + memcpy(clipboard_filtered.Data + clipboard_filtered.Size - out_len, c_utf8, out_len); } - clipboard_filtered[clipboard_filtered_len] = 0; - if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation + if (clipboard_filtered.Size > 0) // If everything was filtered, ignore the pasting operation { - stb_textedit_paste(state, state->Stb, clipboard_filtered, clipboard_filtered_len); + clipboard_filtered.push_back(0); + stb_textedit_paste(state, state->Stb, clipboard_filtered.Data, clipboard_filtered.Size - 1); state->CursorFollow = true; } - MemFree(clipboard_filtered); } } @@ -4935,31 +4972,29 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ IMSTB_TEXTEDIT_CHARTYPE empty_string; stb_textedit_replace(state, state->Stb, &empty_string, 0); } - else if (strcmp(buf, state->InitialTextA.Data) != 0) + else if (strcmp(buf, state->TextToRevertTo.Data) != 0) { - apply_new_text = state->InitialTextA.Data; - apply_new_text_length = state->InitialTextA.Size - 1; + apply_new_text = state->TextToRevertTo.Data; + apply_new_text_length = state->TextToRevertTo.Size - 1; // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents. // Push records into the undo stack so we can CTRL+Z the revert operation itself value_changed = true; - stb_textedit_replace(state, state->Stb, state->InitialTextA.Data, state->InitialTextA.Size - 1); + stb_textedit_replace(state, state->Stb, state->TextToRevertTo.Data, state->TextToRevertTo.Size - 1); } } - // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer - // before clearing ActiveId, even though strictly speaking it wasn't modified on this frame. - // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. - // This also allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage + // FIXME-OPT: We always reapply the live buffer back to the input buffer before clearing ActiveId, + // even though strictly speaking it wasn't modified on this frame. Should mark dirty state from the stb_textedit callbacks. + // If we do that, need to ensure that as special case, 'validated == true' also writes back. + // This also allows the user to use InputText() without maintaining any user-side storage. // (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object // unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize). - const bool apply_edit_back_to_user_buffer = !revert_edit || (validated && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0); + const bool apply_edit_back_to_user_buffer = true;// !revert_edit || (validated && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0); if (apply_edit_back_to_user_buffer) { - // Apply new value immediately - copy modified buffer back + // Apply current edited text immediately. // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer - // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect. - // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks. // User callback if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0) @@ -5002,14 +5037,15 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ callback_data.UserData = callback_user_data; // FIXME-OPT: Undo stack reconcile needs a backup of the data until we rework API, see #7925 - state->CallbackTextBackup.resize(state->CurLenA + 1); - memcpy(state->CallbackTextBackup.Data, state->TextA.Data, state->CurLenA + 1); - char* callback_buf = is_readonly ? buf : state->TextA.Data; + IM_ASSERT(callback_buf == state->TextSrc); + state->CallbackTextBackup.resize(state->TextLen + 1); + memcpy(state->CallbackTextBackup.Data, callback_buf, state->TextLen + 1); + callback_data.EventKey = event_key; callback_data.Buf = callback_buf; - callback_data.BufTextLen = state->CurLenA; - callback_data.BufSize = state->BufCapacityA; + callback_data.BufTextLen = state->TextLen; + callback_data.BufSize = state->BufCapacity; callback_data.BufDirty = false; const int utf8_cursor_pos = callback_data.CursorPos = state->Stb->cursor; @@ -5022,7 +5058,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Read back what user may have modified callback_buf = is_readonly ? buf : state->TextA.Data; // Pointer may have been invalidated by a resize callback IM_ASSERT(callback_data.Buf == callback_buf); // Invalid to modify those fields - IM_ASSERT(callback_data.BufSize == state->BufCapacityA); + IM_ASSERT(callback_data.BufSize == state->BufCapacity); IM_ASSERT(callback_data.Flags == flags); const bool buf_dirty = callback_data.BufDirty; if (callback_data.CursorPos != utf8_cursor_pos || buf_dirty) { state->Stb->cursor = callback_data.CursorPos; state->CursorFollow = true; } @@ -5032,19 +5068,18 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { // Callback may update buffer and thus set buf_dirty even in read-only mode. IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! - InputTextReconcileUndoStateAfterUserCallback(state, callback_data.Buf, callback_data.BufTextLen); // FIXME: Move the rest of this block inside function and rename to InputTextReconcileStateAfterUserCallback() ? - state->CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() - state->TextA.Size = state->CurLenA + 1; + InputTextReconcileUndoState(state, state->CallbackTextBackup.Data, state->CallbackTextBackup.Size - 1, callback_data.Buf, callback_data.BufTextLen); + state->TextLen = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() state->CursorAnimReset(); } } } // Will copy result string if modified - if (!is_readonly && strcmp(state->TextA.Data, buf) != 0) + if (!is_readonly && strcmp(state->TextSrc, buf) != 0) { - apply_new_text = state->TextA.Data; - apply_new_text_length = state->CurLenA; + apply_new_text = state->TextSrc; + apply_new_text_length = state->TextLen; value_changed = true; } } @@ -5128,7 +5163,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { IM_ASSERT(state != NULL); if (!is_displaying_hint) - buf_display_end = buf_display + state->CurLenA; + buf_display_end = buf_display + state->TextLen; // Render text (with cursor and selection) // This is going to be messy. We need to: @@ -5137,8 +5172,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // - Measure text height (for scrollbar) // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort) // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8. - const char* text_begin = state->TextA.Data; - const char* text_end = text_begin + state->CurLenA; + const char* text_begin = buf_display; + const char* text_end = text_begin + state->TextLen; ImVec2 cursor_offset, select_start_offset; { @@ -5280,14 +5315,19 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_multiline) text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_display_end) * g.FontSize); // We don't need width else if (!is_displaying_hint && g.ActiveId == id) - buf_display_end = buf_display + state->CurLenA; + buf_display_end = buf_display + state->TextLen; else if (!is_displaying_hint) buf_display_end = buf_display + strlen(buf_display); if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) { + // Find render position for right alignment + if (flags & ImGuiInputTextFlags_ElideLeft) + draw_pos.x = ImMin(draw_pos.x, frame_bb.Max.x - CalcTextSize(buf_display, NULL).x - style.FramePadding.x); + + const ImVec2 draw_scroll = /*state ? ImVec2(state->Scroll.x, 0.0f) :*/ ImVec2(0.0f, 0.0f); // Preserve scroll when inactive? ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); - draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect); + draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect); } } @@ -5312,6 +5352,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ g.LastItemData.StatusFlags = item_data_backup.StatusFlags; } } + if (state) + state->TextSrc = NULL; // Log as text if (g.LogEnabled && (!is_password || is_displaying_hint)) @@ -5341,7 +5383,9 @@ void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state) ImStb::StbUndoState* undo_state = &stb_state->undostate; Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId); DebugLocateItemOnHover(state->ID); - Text("CurLenA: %d, Cursor: %d, Selection: %d..%d", state->CurLenA, stb_state->cursor, stb_state->select_start, stb_state->select_end); + Text("CurLenA: %d, Cursor: %d, Selection: %d..%d", state->TextLen, stb_state->cursor, stb_state->select_start, stb_state->select_end); + Text("BufCapacityA: %d", state->BufCapacity); + Text("(Internal Buffer: TextA Size: %d, Capacity: %d)", state->TextA.Size, state->TextA.Capacity); Text("has_preferred_x: %d (%.2f)", stb_state->has_preferred_x, stb_state->preferred_x); Text("undo_point: %d, redo_point: %d, undo_char_point: %d, redo_char_point: %d", undo_state->undo_point, undo_state->redo_point, undo_state->undo_char_point, undo_state->redo_char_point); if (BeginChild("undopoints", ImVec2(0.0f, GetTextLineHeight() * 10), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeY)) // Visualize undo state @@ -5865,7 +5909,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl if ((flags & ImGuiColorEditFlags_NoLabel)) Text("Current"); - ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip; + ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaMask_ | ImGuiColorEditFlags_NoTooltip; ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)); if (ref_col != NULL) { @@ -5905,7 +5949,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl if ((flags & ImGuiColorEditFlags_NoInputs) == 0) { PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x); - ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf; + ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaMask_ | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoSmallPreview; ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker; if (flags & ImGuiColorEditFlags_DisplayRGB || (flags & ImGuiColorEditFlags_DisplayMask_) == 0) if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_DisplayRGB)) @@ -6081,15 +6125,15 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held); - if (flags & ImGuiColorEditFlags_NoAlpha) - flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf); + if (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaOpaque)) + flags &= ~(ImGuiColorEditFlags_AlphaNoBg | ImGuiColorEditFlags_AlphaPreviewHalf); ImVec4 col_rgb = col; if (flags & ImGuiColorEditFlags_InputHSV) ColorConvertHSVtoRGB(col_rgb.x, col_rgb.y, col_rgb.z, col_rgb.x, col_rgb.y, col_rgb.z); ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f); - float grid_step = ImMin(size.x, size.y) / 2.99f; + float grid_step = ImMin(size.x, size.y) / (flags & ImGuiColorEditFlags_LargeAlphaGrid ? 2.f : 2.99f); float rounding = flags & ImGuiColorEditFlags_Round ? ImMin(size_arg.x, size_arg.y) * 0.5f : ImMin(g.Style.FrameRounding, grid_step * 0.5f); ImRect bb_inner = bb; float off = 0.0f; @@ -6101,14 +6145,17 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f) { float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f); - RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawFlags_RoundCornersRight); + if ((flags & ImGuiColorEditFlags_AlphaNoBg) == 0) + RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawFlags_RoundCornersRight); + else + window->DrawList->AddRectFilled(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), rounding, ImDrawFlags_RoundCornersRight); window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundCornersLeft); } else { // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha - ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col_rgb : col_rgb_without_alpha; - if (col_source.w < 1.0f) + ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaOpaque) ? col_rgb_without_alpha : col_rgb; + if (col_source.w < 1.0f && (flags & ImGuiColorEditFlags_AlphaNoBg) == 0) RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding); else window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding); @@ -6138,7 +6185,7 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl // Tooltip if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered && IsItemHovered(ImGuiHoveredFlags_ForTooltip)) - ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)); + ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_AlphaMask_)); return pressed; } @@ -6179,7 +6226,8 @@ void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2); ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); - ColorButton("##preview", cf, (flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz); + ImGuiColorEditFlags flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_AlphaMask_; + ColorButton("##preview", cf, (flags & flags_to_forward) | ImGuiColorEditFlags_NoTooltip, sz); SameLine(); if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags_InputMask_)) { @@ -6495,10 +6543,11 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l // We vertically grow up to current line height up the typical widget height. const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2); const bool span_all_columns = (flags & ImGuiTreeNodeFlags_SpanAllColumns) != 0 && (g.CurrentTable != NULL); + const bool span_all_columns_label = (flags & ImGuiTreeNodeFlags_LabelSpanAllColumns) != 0 && (g.CurrentTable != NULL); ImRect frame_bb; frame_bb.Min.x = span_all_columns ? window->ParentWorkRect.Min.x : (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x; frame_bb.Min.y = window->DC.CursorPos.y; - frame_bb.Max.x = span_all_columns ? window->ParentWorkRect.Max.x : (flags & ImGuiTreeNodeFlags_SpanTextWidth) ? window->DC.CursorPos.x + text_width + padding.x : window->WorkRect.Max.x; + frame_bb.Max.x = span_all_columns ? window->ParentWorkRect.Max.x : (flags & ImGuiTreeNodeFlags_SpanLabelWidth) ? window->DC.CursorPos.x + text_width + padding.x : window->WorkRect.Max.x; frame_bb.Max.y = window->DC.CursorPos.y + frame_height; if (display_frame) { @@ -6512,7 +6561,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing ImRect interact_bb = frame_bb; - if ((flags & (ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_SpanTextWidth | ImGuiTreeNodeFlags_SpanAllColumns)) == 0) + if ((flags & (ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_SpanLabelWidth | ImGuiTreeNodeFlags_SpanAllColumns)) == 0) interact_bb.Max.x = frame_bb.Min.x + text_width + (label_size.x > 0.0f ? style.ItemSpacing.x * 2.0f : 0.0f); // Compute open and multi-select states before ItemAdd() as it clear NextItem data. @@ -6520,7 +6569,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l bool is_open = TreeNodeUpdateNextOpen(storage_id, flags); bool is_visible; - if (span_all_columns) + if (span_all_columns || span_all_columns_label) { // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable.. const float backup_clip_rect_min_x = window->ClipRect.Min.x; @@ -6561,7 +6610,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l return is_open; } - if (span_all_columns) + if (span_all_columns || span_all_columns_label) { TablePushBackgroundChannel(); g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect; @@ -6714,7 +6763,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l LogSetNextTextDecoration(">", NULL); } - if (span_all_columns) + if (span_all_columns && !span_all_columns_label) TablePopBackgroundChannel(); // Label @@ -6722,6 +6771,9 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); else RenderText(text_pos, label, label_end, false); + + if (span_all_columns_label) + TablePopBackgroundChannel(); } if (store_tree_node_stack_data && is_open) @@ -6896,13 +6948,9 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth)) size.x = ImMax(label_size.x, max_x - min_x); - // Text stays at the submission position, but bounding box may be extended on both sides - const ImVec2 text_min = pos; - const ImVec2 text_max(min_x + size.x, pos.y + size.y); - // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable. // FIXME: Not part of layout so not included in clipper calculation, but ItemSize currently doesn't allow offsetting CursorPos. - ImRect bb(min_x, pos.y, text_max.x, text_max.y); + ImRect bb(min_x, pos.y, min_x + size.x, pos.y + size.y); if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0) { const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x; @@ -7017,16 +7065,9 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl const bool highlighted = hovered || (flags & ImGuiSelectableFlags_Highlight); if (highlighted || selected) { - /* fuck this shit - // FIXME-MULTISELECT: Styling: Color for 'selected' elements? ImGuiCol_HeaderSelected - ImU32 col; - if (selected && !highlighted) - col = GetColorU32(ImLerp(GetStyleColorVec4(ImGuiCol_Header), GetStyleColorVec4(ImGuiCol_HeaderHovered), 0.5f)); - else - col = GetColorU32((held && highlighted) ? ImGuiCol_HeaderActive : highlighted ? ImGuiCol_HeaderHovered : ImGuiCol_Header); - */ - const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); - RenderFrame(bb.Min, bb.Max, col, false, style.FrameRounding); + // Between 1.91.0 and 1.91.4 we made selected Selectable use an arbitrary lerp between _Header and _HeaderHovered. Removed that now. (#8106) + ImU32 col = GetColorU32((held && highlighted) ? ImGuiCol_HeaderActive : highlighted ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(bb.Min, bb.Max, col, false, 0.0f); } if (g.NavId == id) { @@ -7045,8 +7086,9 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl PopColumnsBackground(); } + // Text stays at the submission position. Alignment/clipping extents ignore SpanAllColumns. if (is_visible) - RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb); + RenderTextClipped(pos, ImVec2(window->WorkRect.Max.x, pos.y + size.y), label, NULL, &label_size, style.SelectableTextAlign, &bb); // Automatically close popups if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.ItemFlags & ImGuiItemFlags_AutoClosePopups)) @@ -7487,6 +7529,12 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel if (flags & ImGuiMultiSelectFlags_BoxSelect2d) flags &= ~ImGuiMultiSelectFlags_BoxSelect1d; + // FIXME: Workaround to the fact we override CursorMaxPos, meaning size measurement are lost. (#8250) + // They should perhaps be stacked properly? + if (ImGuiTable* table = g.CurrentTable) + if (table->CurrentColumn != -1) + TableEndCell(table); // This is currently safe to call multiple time. If that properly is lost we can extract the "save measurement" part of it. + // FIXME: BeginFocusScope() const ImGuiID id = window->IDStack.back(); ms->Clear(); @@ -8200,15 +8248,10 @@ void ImGuiSelectionExternalStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io) //------------------------------------------------------------------------- // This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label. -// This handle some subtleties with capturing info from the label, but for 99% uses it could essentially be rewritten as: -// if (ImGui::BeginChild("...", ImVec2(ImGui::CalcItemWidth(), ImGui::GetTextLineHeight() * 7.5f), ImGuiChildFlags_FrameStyle)) -// { .... } -// ImGui::EndChild(); -// ImGui::SameLine(); -// ImGui::AlignTextToFramePadding(); -// ImGui::Text("Label"); +// This handle some subtleties with capturing info from the label. +// If you don't need a label you can pretty much directly use ImGui::BeginChild() with ImGuiChildFlags_FrameStyle. // Tip: To have a list filling the entire window width, use size.x = -FLT_MIN and pass an non-visible label e.g. "##empty" -// Tip: If your vertical size is calculated from an item count (e.g. 10 * item_height) consider adding a fractional part to facilitate seeing scrolling boundaries (e.g. 10.25 * item_height). +// Tip: If your vertical size is calculated from an item count (e.g. 10 * item_height) consider adding a fractional part to facilitate seeing scrolling boundaries (e.g. 10.5f * item_height). bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg) { ImGuiContext& g = *GImGui; @@ -8587,12 +8630,13 @@ bool ImGui::BeginMenuBar() IM_ASSERT(!window->DC.MenuBarAppending); BeginGroup(); // Backup position on layer 0 // FIXME: Misleading to use a group for that backup/restore - PushID("##menubar"); + PushID("##MenuBar"); // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect. // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy. + const float border_top = ImMax(window->WindowBorderSize * 0.5f - window->TitleBarHeight, 0.0f); ImRect bar_rect = window->MenuBarRect(); - ImRect clip_rect(IM_ROUND(bar_rect.Min.x + window->WindowBorderSize), IM_ROUND(bar_rect.Min.y + window->WindowBorderSize), IM_ROUND(ImMax(bar_rect.Min.x, bar_rect.Max.x - ImMax(window->WindowRounding, window->WindowBorderSize))), IM_ROUND(bar_rect.Max.y)); + ImRect clip_rect(IM_ROUND(bar_rect.Min.x + window->WindowBorderSize * 0.5f), IM_ROUND(bar_rect.Min.y + border_top), IM_ROUND(ImMax(bar_rect.Min.x, bar_rect.Max.x - ImMax(window->WindowRounding, window->WindowBorderSize * 0.5f))), IM_ROUND(bar_rect.Max.y)); clip_rect.ClipWith(window->OuterRectClipped); PushClipRect(clip_rect.Min, clip_rect.Max, false); @@ -8613,6 +8657,10 @@ void ImGui::EndMenuBar() return; ImGuiContext& g = *GImGui; + IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'" + IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar); + IM_ASSERT(window->DC.MenuBarAppending); + // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings. if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) { @@ -8639,9 +8687,6 @@ void ImGui::EndMenuBar() } } - IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'" - IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar); - IM_ASSERT(window->DC.MenuBarAppending); PopClipRect(); PopID(); window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->Pos.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos. @@ -8710,22 +8755,33 @@ bool ImGui::BeginMainMenuBar() float height = GetFrameHeight(); bool is_open = BeginViewportSideBar("##MainMenuBar", viewport, ImGuiDir_Up, height, window_flags); g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f); - - if (is_open) - BeginMenuBar(); - else + if (!is_open) + { End(); + return false; + } + + // Temporarily disable _NoSavedSettings, in the off-chance that tables or child windows submitted within the menu-bar may want to use settings. (#8356) + g.CurrentWindow->Flags &= ~ImGuiWindowFlags_NoSavedSettings; + BeginMenuBar(); return is_open; } void ImGui::EndMainMenuBar() { + ImGuiContext& g = *GImGui; + if (!g.CurrentWindow->DC.MenuBarAppending) + { + IM_ASSERT_USER_ERROR(0, "Calling EndMainMenuBar() not from a menu-bar!"); // Not technically testing that it is the main menu bar + return; + } + EndMenuBar(); + g.CurrentWindow->Flags |= ImGuiWindowFlags_NoSavedSettings; // Restore _NoSavedSettings (#8356) // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window // FIXME: With this strategy we won't be able to restore a NULL focus. - ImGuiContext& g = *GImGui; - if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest) + if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest && g.ActiveId == 0) FocusTopMostWindowUnderOne(g.NavWindow, NULL, NULL, ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild); End(); @@ -8818,6 +8874,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) float w = label_size.x; ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); pressed = Selectable("", menu_is_open, selectable_flags, ImVec2(w, label_size.y)); + LogSetNextTextDecoration("[", "]"); RenderText(text_pos, label); PopStyleVar(); window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). @@ -8834,6 +8891,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y)); + LogSetNextTextDecoration("", ">"); RenderText(text_pos, label); if (icon_w > 0.0f) RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon); @@ -9047,6 +9105,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut if (shortcut_w > 0.0f) { PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); + LogSetNextTextDecoration("(", ")"); RenderText(pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false); PopStyleColor(); } @@ -9883,7 +9942,7 @@ bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags f IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!"); return false; } - IM_ASSERT(!(flags & ImGuiTabItemFlags_Button)); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead! + IM_ASSERT((flags & ImGuiTabItemFlags_Button) == 0); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead! bool ret = TabItemEx(tab_bar, label, p_open, flags, NULL); if (ret && !(flags & ImGuiTabItemFlags_NoPushId)) @@ -9929,6 +9988,23 @@ bool ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags) return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder, NULL); } +void ImGui::TabItemSpacing(const char* str_id, ImGuiTabItemFlags flags, float width) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + ImGuiTabBar* tab_bar = g.CurrentTabBar; + if (tab_bar == NULL) + { + IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!"); + return; + } + SetNextItemWidth(width); + TabItemEx(tab_bar, str_id, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder | ImGuiTabItemFlags_Invisible, NULL); +} + bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window) { // Layout whole tab bar if not already done @@ -10074,8 +10150,11 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowOverlap); if (g.DragDropActive) button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; - bool hovered, held; - bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); + bool hovered, held, pressed; + if (flags & ImGuiTabItemFlags_Invisible) + hovered = held = pressed = false; + else + pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); if (pressed && !is_tab_button) TabBarQueueFocus(tab_bar, tab); @@ -10107,36 +10186,59 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, #endif // Render tab shape - ImDrawList* display_draw_list = window->DrawList; - const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabDimmed)); - TabItemBackground(display_draw_list, bb, flags, tab_col); - if (tab_contents_visible && (tab_bar->Flags & ImGuiTabBarFlags_DrawSelectedOverline) && style.TabBarOverlineSize > 0.0f) + const bool is_visible = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) && !(flags & ImGuiTabItemFlags_Invisible); + if (is_visible) { - float x_offset = IM_TRUNC(0.4f * style.TabRounding); - if (x_offset < 2.0f * g.CurrentDpiScale) - x_offset = 0.0f; - float y_offset = 1.0f * g.CurrentDpiScale; - display_draw_list->AddLine(bb.GetTL() + ImVec2(x_offset, y_offset), bb.GetTR() + ImVec2(-x_offset, y_offset), GetColorU32(tab_bar_focused ? ImGuiCol_TabSelectedOverline : ImGuiCol_TabDimmedSelectedOverline), style.TabBarOverlineSize); - } - RenderNavCursor(bb, id); + ImDrawList* display_draw_list = window->DrawList; + const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabDimmed)); + TabItemBackground(display_draw_list, bb, flags, tab_col); + if (tab_contents_visible && (tab_bar->Flags & ImGuiTabBarFlags_DrawSelectedOverline) && style.TabBarOverlineSize > 0.0f) + { + // Might be moved to TabItemBackground() ? + ImVec2 tl = bb.GetTL() + ImVec2(0, 1.0f * g.CurrentDpiScale); + ImVec2 tr = bb.GetTR() + ImVec2(0, 1.0f * g.CurrentDpiScale); + ImU32 overline_col = GetColorU32(tab_bar_focused ? ImGuiCol_TabSelectedOverline : ImGuiCol_TabDimmedSelectedOverline); + if (style.TabRounding > 0.0f) + { + float rounding = style.TabRounding; + display_draw_list->PathArcToFast(tl + ImVec2(+rounding, +rounding), rounding, 7, 9); + display_draw_list->PathArcToFast(tr + ImVec2(-rounding, +rounding), rounding, 9, 11); + display_draw_list->PathStroke(overline_col, 0, style.TabBarOverlineSize); + } + else + { + display_draw_list->AddLine(tl - ImVec2(0.5f, 0.5f), tr - ImVec2(0.5f, 0.5f), overline_col, style.TabBarOverlineSize); + } + } + RenderNavCursor(bb, id); - // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget. - const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); - if (tab_bar->SelectedTabId != tab->ID && hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)) && !is_tab_button) - TabBarQueueFocus(tab_bar, tab); + // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget. + const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); + if (tab_bar->SelectedTabId != tab->ID && hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)) && !is_tab_button) + TabBarQueueFocus(tab_bar, tab); - if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton) - flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton; + if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton) + flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton; - // Render tab label, process close button - const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, id) : 0; - bool just_closed; - bool text_clipped; - TabItemLabelAndCloseButton(display_draw_list, bb, tab_just_unsaved ? (flags & ~ImGuiTabItemFlags_UnsavedDocument) : flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped); - if (just_closed && p_open != NULL) - { - *p_open = false; - TabBarCloseTab(tab_bar, tab); + // Render tab label, process close button + const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, id) : 0; + bool just_closed; + bool text_clipped; + TabItemLabelAndCloseButton(display_draw_list, bb, tab_just_unsaved ? (flags & ~ImGuiTabItemFlags_UnsavedDocument) : flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped); + if (just_closed && p_open != NULL) + { + *p_open = false; + TabBarCloseTab(tab_bar, tab); + } + + // Tooltip + // (Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer-> seems ok) + // (We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar, which g.HoveredId ignores) + // FIXME: This is a mess. + // FIXME: We may want disabled tab to still display the tooltip? + if (text_clipped && g.HoveredId == id && !held) + if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip)) + SetItemTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label); } // Restore main window position so user can draw there @@ -10144,15 +10246,6 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, PopClipRect(); window->DC.CursorPos = backup_main_cursor_pos; - // Tooltip - // (Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer-> seems ok) - // (We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar, which g.HoveredId ignores) - // FIXME: This is a mess. - // FIXME: We may want disabled tab to still display the tooltip? - if (text_clipped && g.HoveredId == id && !held) - if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip)) - SetItemTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label); - IM_ASSERT(!is_tab_button || !(tab_bar->SelectedTabId == tab->ID && is_tab_button)); // TabItemButton should not be selected if (is_tab_button) return pressed; @@ -10295,6 +10388,7 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, text_ellipsis_clip_bb.Max.x -= unsaved_marker_visible ? (button_sz * 0.80f) : 0.0f; ellipsis_max_x = text_pixel_clip_bb.Max.x; } + LogSetNextTextDecoration("/", "\\"); RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, text_pixel_clip_bb.Max.x, ellipsis_max_x, label, NULL, &label_size); #if 0 diff --git a/Amalgam/src/Core/Core.cpp b/Amalgam/src/Core/Core.cpp index f9a3ff40..e4eaa45b 100644 --- a/Amalgam/src/Core/Core.cpp +++ b/Amalgam/src/Core/Core.cpp @@ -52,12 +52,12 @@ void CCore::Load() return; if (m_bUnload = m_bFailed2 = !U::Hooks.Initialize() || !U::BytePatches.Initialize() || !H::Events.Initialize()) return; - U::ConVars.Initialize(); F::Materials.LoadMaterials(); + U::ConVars.Initialize(); F::Commands.Initialize(); - F::Configs.LoadConfig(F::Configs.sCurrentConfig, false); - F::Menu.ConfigLoaded = true; + F::Configs.LoadConfig(F::Configs.m_sCurrentConfig, false); + F::Configs.m_bConfigLoaded = true; SDK::Output("Amalgam", "Loaded", { 175, 150, 255, 255 }, true, false, false, true); } @@ -85,7 +85,7 @@ void CCore::Unload() ssFailStream << "\n\n\n\n"; std::ofstream file; - file.open(F::Configs.sConfigPath + "\\fail_log.txt", std::ios_base::app); + file.open(F::Configs.m_sConfigPath + "\\fail_log.txt", std::ios_base::app); file << ssFailStream.str(); file.close(); @@ -97,7 +97,7 @@ void CCore::Unload() U::BytePatches.Unload(); H::Events.Unload(); - if (F::Menu.IsOpen) + if (F::Menu.m_bIsOpen) I::MatSystemSurface->SetCursorAlwaysVisible(false); F::Visuals.RestoreWorldModulation(); if (I::Input->CAM_IsThirdPerson()) @@ -126,7 +126,7 @@ void CCore::Unload() ssFailStream << "\n\n\n\n"; std::ofstream file; - file.open(F::Configs.sConfigPath + "\\fail_log.txt", std::ios_base::app); + file.open(F::Configs.m_sConfigPath + "\\fail_log.txt", std::ios_base::app); file << ssFailStream.str(); file.close(); diff --git a/Amalgam/src/Features/Aimbot/Aimbot.cpp b/Amalgam/src/Features/Aimbot/Aimbot.cpp index e6f351af..86657cc1 100644 --- a/Amalgam/src/Features/Aimbot/Aimbot.cpp +++ b/Amalgam/src/Features/Aimbot/Aimbot.cpp @@ -30,7 +30,7 @@ bool CAimbot::ShouldRun(CTFPlayer* pLocal, CTFWeaponBase* pWeapon) return false; } - if (I::EngineVGui->IsGameUIVisible() || I::MatSystemSurface->IsCursorVisible()) + if (I::EngineVGui->IsGameUIVisible()) return false; return true; diff --git a/Amalgam/src/Features/Aimbot/AimbotHitscan/AimbotHitscan.cpp b/Amalgam/src/Features/Aimbot/AimbotHitscan/AimbotHitscan.cpp index b9c60e36..39fe49c8 100644 --- a/Amalgam/src/Features/Aimbot/AimbotHitscan/AimbotHitscan.cpp +++ b/Amalgam/src/Features/Aimbot/AimbotHitscan/AimbotHitscan.cpp @@ -456,8 +456,8 @@ int CAimbotHitscan::CanHit(Target_t& target, CTFPlayer* pLocal, CTFWeaponBase* p if (!bTriggerbot) { float flScale = 0.5f; //Vars::Aimbot::Hitscan::PointScale.Value / 100; - Vec3 vMinsS = vMins * flScale; - Vec3 vMaxsS = vMaxs * flScale; + Vec3 vMinsS = (vMins - vMaxs) / 2 * flScale; + Vec3 vMaxsS = (vMaxs - vMins) / 2 * flScale; vPoints = { Vec3(), diff --git a/Amalgam/src/Features/Aimbot/AimbotProjectile/AimbotProjectile.cpp b/Amalgam/src/Features/Aimbot/AimbotProjectile/AimbotProjectile.cpp index e6a56d97..f3491b84 100644 --- a/Amalgam/src/Features/Aimbot/AimbotProjectile/AimbotProjectile.cpp +++ b/Amalgam/src/Features/Aimbot/AimbotProjectile/AimbotProjectile.cpp @@ -400,7 +400,7 @@ std::vector CAimbotProjectile::GetSplashPoints(Target_t& target, std::v SDK::Trace(vPoint, vTargetEye, MASK_SHOT, &filter, &trace); bNormal = trace.DidHit(); #ifdef SPLASH_DEBUG2 - G::LineStorage.push_back({ { vPoint, trace.endpos }, I::GlobalVars->curtime + 60.f, !bNormal ? Vars::Colors::HealthBar.Value.StartColor : Vars::Colors::HealthBar.Value.EndColor }); + G::LineStorage.push_back({ { vPoint, trace.endpos }, I::GlobalVars->curtime + 60.f, bNormal ? Vars::Colors::IndicatorGood.Value : Vars::Colors::IndicatorBad.Value }); #endif if (!bNormal) { @@ -423,7 +423,7 @@ std::vector CAimbotProjectile::GetSplashPoints(Target_t& target, std::v #ifndef SPLASH_DEBUG2 if (checkPoint(trace, bErase, bNormal)) #else - G::LineStorage.push_back({ { vPoint, trace.endpos }, I::GlobalVars->curtime + 60.f, Vars::Colors::HealthBar.Value.StartColor }); + G::LineStorage.push_back({ { vPoint, trace.endpos }, I::GlobalVars->curtime + 60.f, Vars::Colors::IndicatorBad.Value }); if (checkPoint(trace, bErase, bNormal, &vPoint)) #endif { @@ -451,7 +451,7 @@ std::vector CAimbotProjectile::GetSplashPoints(Target_t& target, std::v #ifndef SPLASH_DEBUG2 if (checkPoint(trace, bErase, bNormal)) #else - G::LineStorage.push_back({ { vFrom, trace.endpos }, I::GlobalVars->curtime + 60.f, bErases ? Vars::Colors::HealthBar.Value.StartColor : Vars::Colors::HealthBar.Value.EndColor }); + G::LineStorage.push_back({ { vFrom, trace.endpos }, I::GlobalVars->curtime + 60.f, bErases ? Vars::Colors::IndicatorBad.Value : Vars::Colors::IndicatorGood.Value }); if (checkPoint(trace, bErase, bNormal, &vPoint)) #endif { @@ -801,8 +801,6 @@ bool CAimbotProjectile::TestAngle(CTFPlayer* pLocal, CTFWeaponBase* pWeapon, Tar } bool bPrimeTime = false; - if (Vars::Aimbot::General::AimType.Value != Vars::Aimbot::General::AimTypeEnum::Smooth) - projInfo.m_vHull += Vec3(Vars::Aimbot::Projectile::HullIncrease.Value, Vars::Aimbot::Projectile::HullIncrease.Value, Vars::Aimbot::Projectile::HullIncrease.Value); const Vec3 vOriginal = target.m_pEntity->GetAbsOrigin(); target.m_pEntity->SetAbsOrigin(target.m_vPos); @@ -983,7 +981,7 @@ int CAimbotProjectile::CanHit(Target_t& target, CTFPlayer* pLocal, CTFWeaponBase F::MoveSim.Initialize(target.m_pEntity, storage); target.m_vPos = target.m_pEntity->m_vecOrigin(); - tInfo.m_flLatency = F::Backtrack.GetReal() + TICKS_TO_TIME(F::Backtrack.GetAnticipatedChoke() + Vars::Aimbot::Projectile::LatencyOffset.Value); + tInfo.m_flLatency = F::Backtrack.GetReal() + TICKS_TO_TIME(F::Backtrack.GetAnticipatedChoke()); tInfo.m_iLatency = TIME_TO_TICKS(tInfo.m_flLatency); iMaxTime = TIME_TO_TICKS(std::min(projInfo.m_flLifetime, Vars::Aimbot::Projectile::PredictionTime.Value)); @@ -1414,6 +1412,9 @@ bool CAimbotProjectile::RunMain(CTFPlayer* pLocal, CTFWeaponBase* pWeapon, CUser void CAimbotProjectile::Run(CTFPlayer* pLocal, CTFWeaponBase* pWeapon, CUserCmd* pCmd) { const bool bSuccess = RunMain(pLocal, pWeapon, pCmd); +#if defined(SPLASH_DEBUG1) || defined(SPLASH_DEBUG2) || defined(SPLASH_DEBUG4) + SDK::Output("Traces", std::format("{}", G::LineStorage.size()).c_str()); +#endif float flAmount = 0.f; if (pWeapon->GetWeaponID() == TF_WEAPON_PIPEBOMBLAUNCHER) diff --git a/Amalgam/src/Features/Aimbot/AutoRocketJump/AutoRocketJump.cpp b/Amalgam/src/Features/Aimbot/AutoRocketJump/AutoRocketJump.cpp index 3436fd98..41d73a56 100644 --- a/Amalgam/src/Features/Aimbot/AutoRocketJump/AutoRocketJump.cpp +++ b/Amalgam/src/Features/Aimbot/AutoRocketJump/AutoRocketJump.cpp @@ -103,7 +103,7 @@ bool CAutoRocketJump::SetAngles(CTFPlayer* pLocal, CTFWeaponBase* pWeapon, CUser void CAutoRocketJump::Run(CTFPlayer* pLocal, CTFWeaponBase* pWeapon, CUserCmd* pCmd) { - if (!pLocal || !pWeapon || !pCmd || !pLocal->IsAlive() || pLocal->IsAGhost() || I::EngineVGui->IsGameUIVisible() || I::MatSystemSurface->IsCursorVisible()) + if (!pLocal || !pWeapon || !pCmd || !pLocal->IsAlive() || pLocal->IsAGhost() || I::EngineVGui->IsGameUIVisible()) { m_iFrame = -1; return; diff --git a/Amalgam/src/Features/Backtrack/Backtrack.h b/Amalgam/src/Features/Backtrack/Backtrack.h index acad4f74..758e5c59 100644 --- a/Amalgam/src/Features/Backtrack/Backtrack.h +++ b/Amalgam/src/Features/Backtrack/Backtrack.h @@ -41,6 +41,7 @@ class CBacktrack void MakeRecords(); void CleanRecords(); + std::unordered_map> m_mRecords; std::unordered_map m_mDidShoot; std::deque m_dSequences; @@ -74,8 +75,6 @@ public: float m_flFakeLatency = 0.f; float m_flFakeInterp = 0.015f; float m_flWishInterp = 0.015f; - - std::unordered_map> m_mRecords; }; ADD_FEATURE(CBacktrack, Backtrack) \ No newline at end of file diff --git a/Amalgam/src/Features/Binds/Binds.cpp b/Amalgam/src/Features/Binds/Binds.cpp index 97109f7a..9d58ffc7 100644 --- a/Amalgam/src/Features/Binds/Binds.cpp +++ b/Amalgam/src/Features/Binds/Binds.cpp @@ -1,6 +1,7 @@ #include "Binds.h" #include "../ImGui/Menu/Menu.h" +#include "../Configs/Configs.h" #include #define IsType(type) var->m_iType == typeid(type).hash_code() @@ -13,7 +14,7 @@ void CBinds::Run(CTFPlayer* pLocal, CTFWeaponBase* pWeapon) { - if (G::Unload) + if (G::Unload || !F::Configs.m_bConfigLoaded) return; auto setVars = [](int iBind) @@ -56,12 +57,12 @@ void CBinds::Run(CTFPlayer* pLocal, CTFWeaponBase* pWeapon) bool bKey = false; switch (tBind.Info) { - case BindEnum::KeyEnum::Hold: bKey = U::KeyHandler.Down(tBind.Key, true, &tBind.Storage); break; - case BindEnum::KeyEnum::Toggle: bKey = U::KeyHandler.Pressed(tBind.Key, true, &tBind.Storage); break; - case BindEnum::KeyEnum::DoubleClick: bKey = U::KeyHandler.Double(tBind.Key, true, &tBind.Storage); break; + case BindEnum::KeyEnum::Hold: bKey = U::KeyHandler.Down(tBind.Key, false, &m_mKeyStorage[tBind.Key]); break; + case BindEnum::KeyEnum::Toggle: bKey = U::KeyHandler.Pressed(tBind.Key, false, &m_mKeyStorage[tBind.Key]); break; + case BindEnum::KeyEnum::DoubleClick: bKey = U::KeyHandler.Double(tBind.Key, false, &m_mKeyStorage[tBind.Key]); break; } const bool bShouldUse = !I::EngineVGui->IsGameUIVisible() && (!I::MatSystemSurface->IsCursorVisible() || I::EngineClient->IsPlayingDemo()) - || F::Menu.IsOpen && !ImGui::GetIO().WantTextInput && !F::Menu.InKeybind; // allow in menu + || F::Menu.m_bIsOpen && !ImGui::GetIO().WantTextInput && !F::Menu.m_bInKeybind; // allow in menu bKey = bShouldUse && bKey; switch (tBind.Info) @@ -121,6 +122,9 @@ void CBinds::Run(CTFPlayer* pLocal, CTFWeaponBase* pWeapon) } }; getBinds(DEFAULT_BIND); + + for (int iKey = 0; iKey < 256; iKey++) + U::KeyHandler.StoreKey(iKey, &m_mKeyStorage[iKey]); } diff --git a/Amalgam/src/Features/Binds/Binds.h b/Amalgam/src/Features/Binds/Binds.h index 49214806..e62e270c 100644 --- a/Amalgam/src/Features/Binds/Binds.h +++ b/Amalgam/src/Features/Binds/Binds.h @@ -17,7 +17,6 @@ struct Bind_t int Info = 0; int Key = 0; bool Not = false; - KeyStorage Storage = {}; bool Active = false; bool Visible = true; @@ -38,6 +37,7 @@ public: void RemoveBind(int iBind, bool bForce = true); std::vector vBinds = {}; + std::unordered_map m_mKeyStorage; }; ADD_FEATURE(CBinds, Binds) \ No newline at end of file diff --git a/Amalgam/src/Features/Commands/Commands.cpp b/Amalgam/src/Features/Commands/Commands.cpp index ee087ebf..6271930a 100644 --- a/Amalgam/src/Features/Commands/Commands.cpp +++ b/Amalgam/src/Features/Commands/Commands.cpp @@ -78,13 +78,13 @@ void CCommands::Initialize() Register("menu", [](const std::deque& args) { - I::MatSystemSurface->SetCursorAlwaysVisible(F::Menu.IsOpen = !F::Menu.IsOpen); + I::MatSystemSurface->SetCursorAlwaysVisible(F::Menu.m_bIsOpen = !F::Menu.m_bIsOpen); }); Register("unload", [](const std::deque& args) { - if (F::Menu.IsOpen) - I::MatSystemSurface->SetCursorAlwaysVisible(F::Menu.IsOpen = false); + if (F::Menu.m_bIsOpen) + I::MatSystemSurface->SetCursorAlwaysVisible(F::Menu.m_bIsOpen = false); U::Core.m_bUnload = true; }); } \ No newline at end of file diff --git a/Amalgam/src/Features/Configs/Configs.cpp b/Amalgam/src/Features/Configs/Configs.cpp index 5fc0a467..252421d4 100644 --- a/Amalgam/src/Features/Configs/Configs.cpp +++ b/Amalgam/src/Features/Configs/Configs.cpp @@ -175,7 +175,7 @@ void CConfigs::LoadJson(boost::property_tree::ptree& mapTree, std::string sName, bool bFound = false; // ensure no duplicates are assigned for (auto& pair : val) { - if (pair.first == sMat) + if (FNV1A::Hash32(pair.first.c_str()) == FNV1A::Hash32(sMat.c_str())) { bFound = true; break; @@ -239,22 +239,22 @@ void CConfigs::LoadJson(boost::property_tree::ptree& mapTree, std::string sName, CConfigs::CConfigs() { - sConfigPath = std::filesystem::current_path().string() + "\\Amalgam"; - sVisualsPath = sConfigPath + "\\Visuals"; + m_sConfigPath = std::filesystem::current_path().string() + "\\Amalgam"; + m_sVisualsPath = m_sConfigPath + "\\Visuals"; - if (!std::filesystem::exists(sConfigPath)) - std::filesystem::create_directory(sConfigPath); + if (!std::filesystem::exists(m_sConfigPath)) + std::filesystem::create_directory(m_sConfigPath); - if (!std::filesystem::exists(sVisualsPath)) - std::filesystem::create_directory(sVisualsPath); + if (!std::filesystem::exists(m_sVisualsPath)) + std::filesystem::create_directory(m_sVisualsPath); // Create 'Core' folder for Attribute-Changer & Playerlist - if (!std::filesystem::exists(sConfigPath + "\\Core")) - std::filesystem::create_directory(sConfigPath + "\\Core"); + if (!std::filesystem::exists(m_sConfigPath + "\\Core")) + std::filesystem::create_directory(m_sConfigPath + "\\Core"); // Create 'Materials' folder for custom materials - if (!std::filesystem::exists(sConfigPath + "\\Materials")) - std::filesystem::create_directory(sConfigPath + "\\Materials"); + if (!std::filesystem::exists(m_sConfigPath + "\\Materials")) + std::filesystem::create_directory(m_sConfigPath + "\\Materials"); } #define IsType(type) var->m_iType == typeid(type).hash_code() @@ -354,8 +354,8 @@ bool CConfigs::SaveConfig(const std::string& sConfigName, bool bNotify) } writeTree.put_child("ConVars", varTree); - write_json(sConfigPath + "\\" + sConfigName + sConfigExtension, writeTree); - sCurrentConfig = sConfigName; sCurrentVisuals = ""; + write_json(m_sConfigPath + "\\" + sConfigName + m_sConfigExtension, writeTree); + m_sCurrentConfig = sConfigName; m_sCurrentVisuals = ""; if (bNotify) F::Notifications.Add("Config " + sConfigName + " saved"); } @@ -371,7 +371,7 @@ bool CConfigs::SaveConfig(const std::string& sConfigName, bool bNotify) bool CConfigs::LoadConfig(const std::string& sConfigName, bool bNotify) { // Check if the config exists - if (!std::filesystem::exists(sConfigPath + "\\" + sConfigName + sConfigExtension)) + if (!std::filesystem::exists(m_sConfigPath + "\\" + sConfigName + m_sConfigExtension)) { // Save default config if one doesn't yet exist if (sConfigName == std::string("default")) @@ -386,7 +386,7 @@ bool CConfigs::LoadConfig(const std::string& sConfigName, bool bNotify) const bool bLoadNosave = GetAsyncKeyState(VK_SHIFT) & 0x8000; boost::property_tree::ptree readTree; - read_json(sConfigPath + "\\" + sConfigName + sConfigExtension, readTree); + read_json(m_sConfigPath + "\\" + sConfigName + m_sConfigExtension, readTree); bool bLegacy = false; if (const auto condTree = readTree.get_child_optional("Binds")) @@ -467,7 +467,7 @@ bool CConfigs::LoadConfig(const std::string& sConfigName, bool bNotify) H::Fonts.Reload(Vars::Menu::Scale.Map[DEFAULT_BIND]); - sCurrentConfig = sConfigName; sCurrentVisuals = ""; + m_sCurrentConfig = sConfigName; m_sCurrentVisuals = ""; if (bNotify) F::Notifications.Add("Config " + sConfigName + " loaded"); } @@ -511,7 +511,7 @@ bool CConfigs::SaveVisual(const std::string& sConfigName, bool bNotify) else SaveMisc(WindowBox_t, writeTree) } - write_json(sConfigPath + "\\Visuals\\" + sConfigName + sConfigExtension, writeTree); + write_json(m_sConfigPath + "\\Visuals\\" + sConfigName + m_sConfigExtension, writeTree); if (bNotify) F::Notifications.Add("Visual config " + sConfigName + " saved"); } @@ -526,7 +526,7 @@ bool CConfigs::SaveVisual(const std::string& sConfigName, bool bNotify) bool CConfigs::LoadVisual(const std::string& sConfigName, bool bNotify) { // Check if the visual config exists - if (!std::filesystem::exists(sVisualsPath + "\\" + sConfigName + sConfigExtension)) + if (!std::filesystem::exists(m_sVisualsPath + "\\" + sConfigName + m_sConfigExtension)) { //if (sConfigName == std::string("default")) // SaveVisual("default"); @@ -538,7 +538,7 @@ bool CConfigs::LoadVisual(const std::string& sConfigName, bool bNotify) const bool bLoadNosave = GetAsyncKeyState(VK_SHIFT) & 0x8000; boost::property_tree::ptree readTree; - read_json(sConfigPath + "\\Visuals\\" + sConfigName + sConfigExtension, readTree); + read_json(m_sConfigPath + "\\Visuals\\" + sConfigName + m_sConfigExtension, readTree); for (auto& var : g_Vars) { @@ -558,7 +558,7 @@ bool CConfigs::LoadVisual(const std::string& sConfigName, bool bNotify) else LoadMisc(WindowBox_t, readTree) } - sCurrentVisuals = sConfigName; + m_sCurrentVisuals = sConfigName; if (bNotify) F::Notifications.Add("Visual config " + sConfigName + " loaded"); } @@ -579,7 +579,7 @@ void CConfigs::RemoveConfig(const std::string& sConfigName, bool bNotify) { if (FNV1A::Hash32(sConfigName.c_str()) != FNV1A::Hash32Const("default")) { - std::filesystem::remove(sConfigPath + "\\" + sConfigName + sConfigExtension); + std::filesystem::remove(m_sConfigPath + "\\" + sConfigName + m_sConfigExtension); LoadConfig("default", false); @@ -599,7 +599,7 @@ void CConfigs::RemoveVisual(const std::string& sConfigName, bool bNotify) { try { - std::filesystem::remove(sVisualsPath + "\\" + sConfigName + sConfigExtension); + std::filesystem::remove(m_sVisualsPath + "\\" + sConfigName + m_sConfigExtension); if (bNotify) F::Notifications.Add("Visual config " + sConfigName + " deleted"); diff --git a/Amalgam/src/Features/Configs/Configs.h b/Amalgam/src/Features/Configs/Configs.h index e0fcac4c..300f9f2e 100644 --- a/Amalgam/src/Features/Configs/Configs.h +++ b/Amalgam/src/Features/Configs/Configs.h @@ -46,11 +46,13 @@ public: boost::property_tree::ptree ColorToTree(const Color_t& color); void TreeToColor(const boost::property_tree::ptree& tree, Color_t& out); - std::string sCurrentConfig = "default"; - std::string sCurrentVisuals = "default"; - std::string sConfigPath; - std::string sVisualsPath; - const std::string sConfigExtension = ".json"; + std::string m_sCurrentConfig = "default"; + std::string m_sCurrentVisuals = "default"; + std::string m_sConfigPath; + std::string m_sVisualsPath; + const std::string m_sConfigExtension = ".json"; + + bool m_bConfigLoaded = false; }; ADD_FEATURE(CConfigs, Configs); \ No newline at end of file diff --git a/Amalgam/src/Features/CritHack/CritHack.cpp b/Amalgam/src/Features/CritHack/CritHack.cpp index 3b53a3ff..76aa5c28 100644 --- a/Amalgam/src/Features/CritHack/CritHack.cpp +++ b/Amalgam/src/Features/CritHack/CritHack.cpp @@ -11,10 +11,14 @@ void CCritHack::Fill(const CUserCmd* pCmd, int n) { - static int iStart = pCmd->command_number; + if (!m_iFillStart) + m_iFillStart = pCmd->command_number; for (auto& [iSlot, tStorage] : m_mStorage) { + if (!tStorage.m_bActive) + continue; + for (auto it = tStorage.m_vCritCommands.begin(); it != tStorage.m_vCritCommands.end();) { if (*it <= pCmd->command_number) @@ -35,7 +39,7 @@ void CCritHack::Fill(const CUserCmd* pCmd, int n) if (tStorage.m_vCritCommands.size() >= unsigned(n)) break; - const int iCmdNum = iStart + i; + const int iCmdNum = m_iFillStart + i; if (IsCritCommand(iSlot, tStorage.m_iEntIndex, tStorage.m_flMultCritChance, iCmdNum)) tStorage.m_vCritCommands.push_back(iCmdNum); } @@ -44,13 +48,13 @@ void CCritHack::Fill(const CUserCmd* pCmd, int n) if (tStorage.m_vSkipCommands.size() >= unsigned(n)) break; - const int iCmdNum = iStart + i; + const int iCmdNum = m_iFillStart + i; if (IsCritCommand(iSlot, tStorage.m_iEntIndex, tStorage.m_flMultCritChance, iCmdNum, false)) tStorage.m_vSkipCommands.push_back(iCmdNum); } } - iStart += n; + m_iFillStart += n; } @@ -263,7 +267,8 @@ void CCritHack::ResetWeapons(CTFPlayer* pLocal) if (!pLocal->m_hMyWeapons()) return; - std::unordered_map mWeapons = {}; + for (auto& [iSlot, tStorage] : m_mStorage) + tStorage.m_bActive = false; for (int i = 0; i < MAX_WEAPONS; i++) { @@ -273,23 +278,14 @@ void CCritHack::ResetWeapons(CTFPlayer* pLocal) int iSlot = pWeapon->GetSlot(); auto& tStorage = m_mStorage[iSlot]; - mWeapons[iSlot] = true; + tStorage.m_bActive = true; int iEntIndex = pWeapon->entindex(); int iDefIndex = pWeapon->m_iItemDefinitionIndex(); float flMultCritChance = SDK::AttribHookValue(1.f, "mult_crit_chance", pWeapon); if (tStorage.m_iEntIndex != iEntIndex || tStorage.m_iDefIndex != iDefIndex || tStorage.m_flMultCritChance != flMultCritChance) - { tStorage = { iEntIndex, iDefIndex, flMultCritChance }; - SDK::Output("Crithack", std::format("Resetting weapon {}", iDefIndex).c_str(), { 0, 255, 255, 255 }, Vars::Debug::Logging.Value); - } - } - - for (auto& [iSlot, _] : m_mStorage) - { - if (!mWeapons.contains(iSlot)) - m_mStorage.erase(iSlot); } } @@ -297,14 +293,14 @@ void CCritHack::Reset() { m_mStorage = {}; + m_iFillStart = 0; + m_iCritDamage = 0; m_iAllDamage = 0; m_bCritBanned = false; m_iDamageTilUnban = 0; m_flCritChance = 0.f; - - SDK::Output("Crithack", "Resetting all", { 0, 255, 255, 255 }, Vars::Debug::Logging.Value); } diff --git a/Amalgam/src/Features/CritHack/CritHack.h b/Amalgam/src/Features/CritHack/CritHack.h index 263c9a89..6293d462 100644 --- a/Amalgam/src/Features/CritHack/CritHack.h +++ b/Amalgam/src/Features/CritHack/CritHack.h @@ -18,6 +18,8 @@ struct WeaponStorage_t std::deque m_vCritCommands = {}; std::deque m_vSkipCommands = {}; + + bool m_bActive = false; }; class CCritHack @@ -35,6 +37,8 @@ private: void ResetWeapons(CTFPlayer* pLocal); void Reset(); + int m_iFillStart = 0; + int m_iCritDamage = 0.f; int m_iAllDamage = 0.f; std::unordered_map m_mHealthStorage = {}; diff --git a/Amalgam/src/Features/ImGui/Menu/Components.h b/Amalgam/src/Features/ImGui/Menu/Components.h index 2f6e1a52..c5877091 100644 --- a/Amalgam/src/Features/ImGui/Menu/Components.h +++ b/Amalgam/src/Features/ImGui/Menu/Components.h @@ -1414,7 +1414,7 @@ namespace ImGui PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, { H::Draw.Scale(4), 0 }); PushStyleColor(ImGuiCol_PopupBg, F::Render.Foreground.Value); ImVec4 tempColor = ColorToVec(*tColor); - bool bReturn = ColorEdit4(std::format("##{}", sLabel).c_str(), &tempColor.x, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoBorder | ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_Round, vSize); + bool bReturn = ColorEdit4(std::format("##{}", sLabel).c_str(), &tempColor.x, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoBorder | ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_Round | ImGuiColorEditFlags_LargeAlphaGrid, vSize); if (bReturn) *tColor = VecToColor(tempColor); PopStyleColor(); @@ -1584,7 +1584,7 @@ namespace ImGui if (GetActiveID() == uId) { - F::Menu.InKeybind = true; + F::Menu.m_bInKeybind = true; //FButton("...", flags | FButton_NoUpper, sizeOffset); FButton(std::format("{}: ...", sLabel).c_str(), iFlags | FButton_NoUpper, vSize, iSizeOffset); diff --git a/Amalgam/src/Features/ImGui/Menu/Menu.cpp b/Amalgam/src/Features/ImGui/Menu/Menu.cpp index 1129322a..9e334906 100644 --- a/Amalgam/src/Features/ImGui/Menu/Menu.cpp +++ b/Amalgam/src/Features/ImGui/Menu/Menu.cpp @@ -232,8 +232,6 @@ void CMenu::MenuAimbot() FSlider("high min distance##ground", Vars::Aimbot::Projectile::GroundHighMinimumDistance, 0.f, 10000.f, 100.f, "%g", FSlider_Right | FSlider_Min | FSlider_Precision); FSlider("max changes##ground", Vars::Aimbot::Projectile::GroundMaxChanges, 0, 5, 1, "%i", FSlider_Left | FSlider_Min | FSlider_Precision); FSlider("max change time##ground", Vars::Aimbot::Projectile::GroundMaxChangeTime, 0, 66, 1, "%i", FSlider_Right | FSlider_Min | FSlider_Precision); - FSlider("new weight##ground", Vars::Aimbot::Projectile::GroundNewWeight, 0.f, 200.f, 5.f, "%g%%", FSlider_Left | FSlider_Min | FSlider_Precision); - FSlider("old weight##ground", Vars::Aimbot::Projectile::GroundOldWeight, 0.f, 200.f, 5.f, "%g%%", FSlider_Right | FSlider_Min | FSlider_Precision); FText("air"); FSlider("samples##air", Vars::Aimbot::Projectile::AirSamples, 3, 66, 1, "%i", FSlider_Left); @@ -244,14 +242,10 @@ void CMenu::MenuAimbot() FSlider("high min distance##air", Vars::Aimbot::Projectile::AirHighMinimumDistance, 0.f, 10000.f, 100.f, "%g", FSlider_Right | FSlider_Min | FSlider_Precision); FSlider("max changes##air", Vars::Aimbot::Projectile::AirMaxChanges, 0, 5, 1, "%i", FSlider_Left | FSlider_Min | FSlider_Precision); FSlider("max change time##air", Vars::Aimbot::Projectile::AirMaxChangeTime, 0, 66, 1, "%i", FSlider_Right | FSlider_Min | FSlider_Precision); - FSlider("new weight##air", Vars::Aimbot::Projectile::AirNewWeight, 0.f, 200.f, 5.f, "%g%%", FSlider_Left | FSlider_Min | FSlider_Precision); - FSlider("old weight##air", Vars::Aimbot::Projectile::AirOldWeight, 0.f, 200.f, 5.f, "%g%%", FSlider_Right | FSlider_Min | FSlider_Precision); FText(""); FSlider("velocity average count", Vars::Aimbot::Projectile::VelocityAverageCount, 1, 10, 1, "%i", FSlider_Left); FSlider("vertical shift", Vars::Aimbot::Projectile::VerticalShift, 0.f, 10.f, 0.5f, "%g", FSlider_Right | FSlider_Min | FSlider_Precision); - FSlider("latency offset", Vars::Aimbot::Projectile::LatencyOffset, -1.f, 1.f, 0.1f, "%g", FSlider_Left | FSlider_Precision); - FSlider("hull increase", Vars::Aimbot::Projectile::HullIncrease, 0.f, 3.f, 0.5f, "%g", FSlider_Right | FSlider_Min | FSlider_Precision); FSlider("drag override", Vars::Aimbot::Projectile::DragOverride, 0.f, 1.f, 0.01f, "%g", FSlider_Left | FSlider_Min | FSlider_Precision); FSlider("time override", Vars::Aimbot::Projectile::TimeOverride, 0.f, 2.f, 0.01f, "%g", FSlider_Right | FSlider_Min | FSlider_Precision); @@ -269,9 +263,7 @@ void CMenu::MenuAimbot() FSlider("direct splash count", Vars::Aimbot::Projectile::SplashCountDirect, 1, 100, 1, "%i", FSlider_Left | FSlider_Min); FSlider("arc splash count", Vars::Aimbot::Projectile::SplashCountArc, 1, 100, 1, "%i", FSlider_Right | FSlider_Min); FSlider("splash trace interval", Vars::Aimbot::Projectile::SplashTraceInterval, 1, 10, 1, "%i", FSlider_Left); - FSlider("delta count", Vars::Aimbot::Projectile::DeltaCount, 1, 5, 1, "%i", FSlider_Right); - FToggle("strafe delta", Vars::Aimbot::Projectile::StrafeDelta); - FTooltip("this was a test and should probably stay off"); + FSlider("delta count", Vars::Aimbot::Projectile::DeltaCount, 1, 5, 1, "%i", FSlider_Left); FDropdown("delta mode", Vars::Aimbot::Projectile::DeltaMode, { "average", "max" }, {}, FDropdown_Right); } EndSection(); } @@ -1020,10 +1012,8 @@ void CMenu::MenuVisuals() if (Section("Indicators")) { FDropdown("Indicators", Vars::Menu::Indicators, { "Ticks", "Crit hack", "Spectators", "Ping", "Conditions", "Seed prediction" }, {}, FDropdown_Multi); - PushDisabled(I::EngineClient->IsConnected()); // seems to cause crashes while changing ingame - if (FSlider("Scale", Vars::Menu::Scale, 0.75f, 2.f, 0.25f, "%g", FSlider_Min | FSlider_Precision | FSlider_NoAutoUpdate)) - H::Fonts.Reload(Vars::Menu::Scale.Map[DEFAULT_BIND]); - PopDisabled(); + if (FSlider("Scale", Vars::Menu::Scale, 0.75f, 2.f, 0.25f, "%g", FSlider_Min | FSlider_Precision | FSlider_NoAutoUpdate)) + H::Fonts.Reload(Vars::Menu::Scale.Map[DEFAULT_BIND]); FToggle("Cheap text", Vars::Menu::CheapText); } EndSection(); @@ -1302,9 +1292,9 @@ void CMenu::MenuSettings() if (Section("Config")) { if (FButton("Configs folder", FButton_Left)) - ShellExecuteA(NULL, NULL, F::Configs.sConfigPath.c_str(), NULL, NULL, SW_SHOWNORMAL); + ShellExecuteA(NULL, NULL, F::Configs.m_sConfigPath.c_str(), NULL, NULL, SW_SHOWNORMAL); if (FButton("Visuals folder", FButton_Right | FButton_SameLine)) - ShellExecuteA(NULL, NULL, F::Configs.sVisualsPath.c_str(), NULL, NULL, SW_SHOWNORMAL); + ShellExecuteA(NULL, NULL, F::Configs.m_sVisualsPath.c_str(), NULL, NULL, SW_SHOWNORMAL); static int iCurrentType = 0; FTabs({ "GENERAL", "VISUALS", }, &iCurrentType, { GetColumnWidth() / 2, H::Draw.Scale(40) }, { H::Draw.Scale(8), GetCursorPos().y }, false); @@ -1318,20 +1308,20 @@ void CMenu::MenuSettings() FSDropdown("Config name", &newName, {}, FSDropdown_AutoUpdate | FDropdown_Left); if (FButton("Create", FButton_Fit | FButton_SameLine, { 0, 40 }) && newName.length() > 0) { - if (!std::filesystem::exists(F::Configs.sConfigPath + "\\" + newName)) + if (!std::filesystem::exists(F::Configs.m_sConfigPath + "\\" + newName)) F::Configs.SaveConfig(newName); newName.clear(); } std::vector> vConfigs = {}; bool bDefaultFound = false; - for (auto& entry : std::filesystem::directory_iterator(F::Configs.sConfigPath)) + for (auto& entry : std::filesystem::directory_iterator(F::Configs.m_sConfigPath)) { - if (!entry.is_regular_file() || entry.path().extension() != F::Configs.sConfigExtension) + if (!entry.is_regular_file() || entry.path().extension() != F::Configs.m_sConfigExtension) continue; std::string sConfigName = entry.path().filename().string(); - sConfigName.erase(sConfigName.end() - F::Configs.sConfigExtension.size(), sConfigName.end()); + sConfigName.erase(sConfigName.end() - F::Configs.m_sConfigExtension.size(), sConfigName.end()); if (FNV1A::Hash32(sConfigName.c_str()) == FNV1A::Hash32Const("default")) bDefaultFound = true; @@ -1352,7 +1342,7 @@ void CMenu::MenuSettings() for (auto& [entry, sConfigName] : vConfigs) { - bool bCurrentConfig = FNV1A::Hash32(sConfigName.c_str()) == FNV1A::Hash32(F::Configs.sCurrentConfig.c_str()); + bool bCurrentConfig = FNV1A::Hash32(sConfigName.c_str()) == FNV1A::Hash32(F::Configs.m_sCurrentConfig.c_str()); ImVec2 vOriginalPos = GetCursorPos(); SetCursorPos({ H::Draw.Scale(14), vOriginalPos.y + H::Draw.Scale(11) }); @@ -1366,7 +1356,7 @@ void CMenu::MenuSettings() SetCursorPos({ GetWindowWidth() - H::Draw.Scale(++iOffset) * 25, vOriginalPos.y + H::Draw.Scale(9) }); if (IconButton(ICON_MD_SAVE)) { - if (!bCurrentConfig || F::Configs.sCurrentVisuals.length()) + if (!bCurrentConfig || F::Configs.m_sCurrentVisuals.length()) OpenPopup(std::format("Confirmation## SaveConfig{}", sConfigName).c_str()); else F::Configs.SaveConfig(sConfigName); @@ -1424,20 +1414,20 @@ void CMenu::MenuSettings() FSDropdown("Config name", &newName, {}, FSDropdown_AutoUpdate | FDropdown_Left); if (FButton("Create", FButton_Fit | FButton_SameLine, { 0, 40 }) && newName.length() > 0) { - if (!std::filesystem::exists(F::Configs.sVisualsPath + "\\" + newName)) + if (!std::filesystem::exists(F::Configs.m_sVisualsPath + "\\" + newName)) F::Configs.SaveVisual(newName); newName.clear(); } - for (auto& entry : std::filesystem::directory_iterator(F::Configs.sVisualsPath)) + for (auto& entry : std::filesystem::directory_iterator(F::Configs.m_sVisualsPath)) { - if (!entry.is_regular_file() || entry.path().extension() != F::Configs.sConfigExtension) + if (!entry.is_regular_file() || entry.path().extension() != F::Configs.m_sConfigExtension) continue; std::string sConfigName = entry.path().filename().string(); - sConfigName.erase(sConfigName.end() - F::Configs.sConfigExtension.size(), sConfigName.end()); + sConfigName.erase(sConfigName.end() - F::Configs.m_sConfigExtension.size(), sConfigName.end()); - bool bCurrentConfig = FNV1A::Hash32(sConfigName.c_str()) == FNV1A::Hash32(F::Configs.sCurrentVisuals.c_str()); + bool bCurrentConfig = FNV1A::Hash32(sConfigName.c_str()) == FNV1A::Hash32(F::Configs.m_sCurrentVisuals.c_str()); ImVec2 vOriginalPos = GetCursorPos(); SetCursorPos({ H::Draw.Scale(14), vOriginalPos.y + H::Draw.Scale(11) }); @@ -2501,7 +2491,7 @@ void CMenu::MenuSettings() LockedMaterial = pair.m_bLocked; TextEditor.SetText(F::Materials.GetVMT(FNV1A::Hash32(CurrentMaterial.c_str()))); - TextEditor.SetReadOnly(LockedMaterial); + TextEditor.SetReadOnlyEnabled(LockedMaterial); } SetCursorPos(vOriginalPos); DebugDummy({ 0, H::Draw.Scale(28) }); @@ -2512,8 +2502,8 @@ void CMenu::MenuSettings() TableNextColumn(); if (CurrentMaterial.length()) { - auto count = std::ranges::count(TextEditor.GetText(), '\n'); // doesn't account for text editor size otherwise - if (Section("Editor", H::Draw.Scale(83) + 6 + FCalcTextSize("A", F::Render.FontMono).y * count, true)) + SetCursorPosY(GetScrollY() + GetStyle().WindowPadding.y); + if (Section("Editor", GetWindowSize().y - GetStyle().WindowPadding.y * 2, true)) { // Toolbar if (!LockedMaterial) @@ -2521,7 +2511,6 @@ void CMenu::MenuSettings() if (FButton("Save", FButton_Fit)) { auto sText = TextEditor.GetText(); - sText.erase(sText.end() - 1, sText.end()); // get rid of random newline F::Materials.EditMaterial(CurrentMaterial.c_str(), sText.c_str()); } SameLine(); @@ -2537,6 +2526,17 @@ void CMenu::MenuSettings() DebugDummy({ 0, H::Draw.Scale(8) }); PushFont(F::Render.FontMono); + TextEditor.SetLanguageDefinition(TextEditor::LanguageDefinitionId::Cpp); + TextEditor.SetPaletteIndex(TextEditor::PaletteIndex::Background, F::Render.Background); + TextEditor.SetPaletteIndex(TextEditor::PaletteIndex::Default, F::Render.Active); + TextEditor.SetPaletteIndex(TextEditor::PaletteIndex::Identifier, F::Render.Active); + TextEditor.SetPaletteIndex(TextEditor::PaletteIndex::Cursor, F::Render.Active); + TextEditor.SetPaletteIndex(TextEditor::PaletteIndex::LineNumber, F::Render.Inactive); + TextEditor.SetPaletteIndex(TextEditor::PaletteIndex::Comment, F::Render.Inactive); + TextEditor.SetPaletteIndex(TextEditor::PaletteIndex::MultiLineComment, F::Render.Inactive); + TextEditor.SetPaletteIndex(TextEditor::PaletteIndex::Punctuation, F::Render.Inactive); + TextEditor.SetPaletteIndex(TextEditor::PaletteIndex::ControlCharacter, ImColor(F::Render.Inactive.Value.x, F::Render.Inactive.Value.y, F::Render.Inactive.Value.z, 0.1f)); + TextEditor.SetPaletteIndex(TextEditor::PaletteIndex::String, F::Render.Accent); TextEditor.Render("TextEditor"); PopFont(); } EndSection(); @@ -2595,11 +2595,11 @@ void CMenu::DrawBinds() { using namespace ImGui; - if (IsOpen ? false : !Vars::Menu::ShowBinds.Value || I::EngineVGui->IsGameUIVisible() || I::MatSystemSurface->IsCursorVisible() && !I::EngineClient->IsPlayingDemo()) + if (m_bIsOpen ? false : !Vars::Menu::ShowBinds.Value || I::EngineVGui->IsGameUIVisible() || I::MatSystemSurface->IsCursorVisible() && !I::EngineClient->IsPlayingDemo()) return; static DragBox_t old = { -2147483648, -2147483648 }; - DragBox_t info = IsOpen ? FGet(Vars::Menu::BindsDisplay) : Vars::Menu::BindsDisplay.Value; + DragBox_t info = m_bIsOpen ? FGet(Vars::Menu::BindsDisplay) : Vars::Menu::BindsDisplay.Value; if (info != old) SetNextWindowPos({ float(info.x), float(info.y) }, ImGuiCond_Always); @@ -2613,7 +2613,7 @@ void CMenu::DrawBinds() if (iParent != tBind.Parent) continue; - if (tBind.Visible || IsOpen) + if (tBind.Visible || m_bIsOpen) { std::string sType; std::string sInfo; switch (tBind.Type) @@ -2662,7 +2662,7 @@ void CMenu::DrawBinds() vBinds.push_back({ tBind.Active, tBind.Name.c_str(), sType, sInfo, iBind, tBind }); } - if (tBind.Active || IsOpen) + if (tBind.Active || m_bIsOpen) getBinds(iBind); } }; @@ -2671,7 +2671,7 @@ void CMenu::DrawBinds() float flNameWidth = 0, flInfoWidth = 0, flStateWidth = 0; if (vBinds.empty()) { - if (!IsOpen) + if (!m_bIsOpen) return; else flNameWidth = FCalcTextSize("Binds", F::Render.FontLarge).x + H::Draw.Scale(10); @@ -2689,7 +2689,7 @@ void CMenu::DrawBinds() flNameWidth += H::Draw.Scale(9), flInfoWidth += H::Draw.Scale(9), flStateWidth += H::Draw.Scale(9); } - float flWidth = flNameWidth + flInfoWidth + flStateWidth + (IsOpen ? H::Draw.Scale(88) : H::Draw.Scale(14)); + float flWidth = flNameWidth + flInfoWidth + flStateWidth + (m_bIsOpen ? H::Draw.Scale(88) : H::Draw.Scale(14)); float flHeight = H::Draw.Scale(18 * vBinds.size() + 40); SetNextWindowSize({ flWidth, flHeight }); PushStyleVar(ImGuiStyleVar_WindowMinSize, { H::Draw.Scale(40), H::Draw.Scale(40) }); @@ -2699,7 +2699,7 @@ void CMenu::DrawBinds() ImVec2 vWindowPos = GetWindowPos(); info.x = vWindowPos.x; info.y = vWindowPos.y; old = info; - if (IsOpen) + if (m_bIsOpen) FSet(Vars::Menu::BindsDisplay, info); PushFont(F::Render.FontLarge); @@ -2727,7 +2727,7 @@ void CMenu::DrawBinds() FText(sState.c_str()); PopStyleColor(); - if (IsOpen) + if (m_bIsOpen) { // buttons SetCursorPos({ flWidth - H::Draw.Scale(25), H::Draw.Scale(33 + 18 * i) }); bool bDelete = IconButton(ICON_MD_DELETE); @@ -2864,19 +2864,30 @@ void CMenu::Render() using namespace ImGui; for (int iKey = 0; iKey < 256; iKey++) + { U::KeyHandler.StoreKey(iKey); - if (!ConfigLoaded || !(ImGui::GetIO().DisplaySize.x > 160.f && ImGui::GetIO().DisplaySize.y > 28.f)) + // don't drop inputs for binds + auto& tMainKey = U::KeyHandler.m_mKeyStorage[iKey]; + auto& tBindKey = F::Binds.m_mKeyStorage[iKey]; + tBindKey.m_bIsDown = tMainKey.m_bIsDown || tBindKey.m_bIsDown; + tBindKey.m_bIsPressed = tMainKey.m_bIsPressed || tBindKey.m_bIsPressed; + tBindKey.m_bIsDouble = tMainKey.m_bIsDouble || tBindKey.m_bIsDouble; + tBindKey.m_bIsReleased = tMainKey.m_bIsReleased || tBindKey.m_bIsReleased; + tBindKey.m_dPressTime = tMainKey.m_dPressTime; + } + + if (!F::Configs.m_bConfigLoaded || !(ImGui::GetIO().DisplaySize.x > 160.f && ImGui::GetIO().DisplaySize.y > 28.f)) return; - InKeybind = false; + m_bInKeybind = false; if (U::KeyHandler.Pressed(Vars::Menu::MenuPrimaryKey.Value) || U::KeyHandler.Pressed(Vars::Menu::MenuSecondaryKey.Value)) - I::MatSystemSurface->SetCursorAlwaysVisible(IsOpen = !IsOpen); + I::MatSystemSurface->SetCursorAlwaysVisible(m_bIsOpen = !m_bIsOpen); PushFont(F::Render.FontRegular); DrawBinds(); - if (IsOpen) + if (m_bIsOpen) { DrawMenu(); diff --git a/Amalgam/src/Features/ImGui/Menu/Menu.h b/Amalgam/src/Features/ImGui/Menu/Menu.h index 1ecfd8a6..712c2116 100644 --- a/Amalgam/src/Features/ImGui/Menu/Menu.h +++ b/Amalgam/src/Features/ImGui/Menu/Menu.h @@ -21,9 +21,8 @@ class CMenu public: void Render(); - bool IsOpen = false; - bool ConfigLoaded = false; - bool InKeybind = false; + bool m_bIsOpen = false; + bool m_bInKeybind = false; }; ADD_FEATURE(CMenu, Menu); \ No newline at end of file diff --git a/Amalgam/src/Features/ImGui/Render.cpp b/Amalgam/src/Features/ImGui/Render.cpp index c11a2225..8f02e289 100644 --- a/Amalgam/src/Features/ImGui/Render.cpp +++ b/Amalgam/src/Features/ImGui/Render.cpp @@ -90,6 +90,11 @@ void CRender::LoadFonts() static bool bHasLoaded = false; auto& io = ImGui::GetIO(); + if (bHasLoaded) + { + ImGui_ImplDX9_InvalidateDeviceObjects(); + io.Fonts->ClearFonts(); + } ImFontConfig fontConfig; fontConfig.OversampleH = 2; @@ -99,7 +104,7 @@ void CRender::LoadFonts() FontRegular = io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\verdana.ttf)", H::Draw.Scale(13), &fontConfig, fontRange); FontBold = io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\verdanab.ttf)", H::Draw.Scale(13), &fontConfig, fontRange); FontLarge = io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\verdana.ttf)", H::Draw.Scale(14), &fontConfig, fontRange); - FontMono = io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\verdana.ttf)", H::Draw.Scale(15), &fontConfig, fontRange); + FontMono = io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\cour.ttf)", H::Draw.Scale(16), &fontConfig, fontRange); // windows mono font installed by default #else FontSmall = io.Fonts->AddFontFromMemoryCompressedTTF(RobotoMedium_compressed_data, RobotoMedium_compressed_size, H::Draw.Scale(12), &fontConfig, fontRange); FontRegular = io.Fonts->AddFontFromMemoryCompressedTTF(RobotoMedium_compressed_data, RobotoMedium_compressed_size, H::Draw.Scale(13), &fontConfig, fontRange); @@ -116,8 +121,6 @@ void CRender::LoadFonts() io.Fonts->Build(); io.ConfigDebugHighlightIdConflicts = false; - if (bHasLoaded) - ImGui_ImplDX9_InvalidateDeviceObjects(); bHasLoaded = true; } diff --git a/Amalgam/src/Features/ImGui/Render.h b/Amalgam/src/Features/ImGui/Render.h index 4183836d..dd685b5b 100644 --- a/Amalgam/src/Features/ImGui/Render.h +++ b/Amalgam/src/Features/ImGui/Render.h @@ -16,14 +16,14 @@ public: int Cursor = 2; // Colors - ImColor Accent = { 255, 101, 101 }; - ImColor AccentLight = { 255, 111, 111 }; - ImColor Background = { 23, 23, 23, 250 }; - ImColor Foreground = { 11, 11, 11, 250 }; - ImColor Foremost = { 23, 23, 23, 250 }; - ImColor ForemostLight = { 25, 25, 25, 250 }; - ImColor Inactive = { 150, 150, 150 }; - ImColor Active = { 255, 255, 255 }; + ImColor Accent = {}; + ImColor AccentLight = {}; + ImColor Background = {}; + ImColor Foreground = {}; + ImColor Foremost = {}; + ImColor ForemostLight = {}; + ImColor Inactive = {}; + ImColor Active = {}; // Fonts ImFont* FontSmall = nullptr; diff --git a/Amalgam/src/Features/Players/PlayerCore.cpp b/Amalgam/src/Features/Players/PlayerCore.cpp index 611552da..83c8caf9 100644 --- a/Amalgam/src/Features/Players/PlayerCore.cpp +++ b/Amalgam/src/Features/Players/PlayerCore.cpp @@ -54,7 +54,7 @@ void CPlayerlistCore::SavePlayers() writeTree.put_child("Aliases", aliasTree); // Save the file - write_json(F::Configs.sConfigPath + "\\Core\\Players.json", writeTree); + write_json(F::Configs.m_sConfigPath + "\\Core\\Players.json", writeTree); F::PlayerUtils.m_bSavePlayers = false; } @@ -71,10 +71,10 @@ void CPlayerlistCore::LoadPlayers() try { - if (std::filesystem::exists(F::Configs.sConfigPath + "\\Core\\Players.json")) + if (std::filesystem::exists(F::Configs.m_sConfigPath + "\\Core\\Players.json")) { boost::property_tree::ptree readTree; - read_json(F::Configs.sConfigPath + "\\Core\\Players.json", readTree); + read_json(F::Configs.m_sConfigPath + "\\Core\\Players.json", readTree); F::PlayerUtils.m_mPlayerTags.clear(); bool bNewTags = bool(readTree.get_child_optional("NewTags")); // newer system to support adding default tags better @@ -130,10 +130,10 @@ void CPlayerlistCore::LoadPlayers() } } // support legacy format & convert over - if (std::filesystem::exists(F::Configs.sConfigPath + "\\Core\\Playerlist.json")) + if (std::filesystem::exists(F::Configs.m_sConfigPath + "\\Core\\Playerlist.json")) { boost::property_tree::ptree readTree; - read_json(F::Configs.sConfigPath + "\\Core\\Playerlist.json", readTree); + read_json(F::Configs.m_sConfigPath + "\\Core\\Playerlist.json", readTree); for (auto& it : readTree) { @@ -188,7 +188,7 @@ void CPlayerlistCore::SaveTags() writeTree.put_child("Tags", tagTree); // Save the file - write_json(F::Configs.sConfigPath + "\\Core\\Tags.json", writeTree); + write_json(F::Configs.m_sConfigPath + "\\Core\\Tags.json", writeTree); F::PlayerUtils.m_bSaveTags = false; } @@ -205,10 +205,10 @@ void CPlayerlistCore::LoadTags() try { - if (std::filesystem::exists(F::Configs.sConfigPath + "\\Core\\Tags.json")) + if (std::filesystem::exists(F::Configs.m_sConfigPath + "\\Core\\Tags.json")) { boost::property_tree::ptree readTree; - read_json(F::Configs.sConfigPath + "\\Core\\Tags.json", readTree); + read_json(F::Configs.m_sConfigPath + "\\Core\\Tags.json", readTree); F::PlayerUtils.m_vTags = { { "Default", { 200, 200, 200, 255 }, 0, false, false, true }, { "Ignored", { 200, 200, 200, 255 }, -1, false, true, true }, diff --git a/Amalgam/src/Features/Records/Records.cpp b/Amalgam/src/Features/Records/Records.cpp index 3b352647..90fca5d3 100644 --- a/Amalgam/src/Features/Records/Records.cpp +++ b/Amalgam/src/Features/Records/Records.cpp @@ -124,7 +124,9 @@ void CRecords::Event(IGameEvent* pEvent, uint32_t uHash, CTFPlayer* pLocal) { sID.replace(0, 5, ""); sID.pop_back(); - int iID = std::stoi(sID); + uint32_t iID = std::stoi(sID); + if (H::Entities.InParty(iID)) // ignore party + return; auto sName = pEvent->GetString("name"); TagsOnJoin(sName, iID); @@ -146,7 +148,8 @@ void CRecords::Event(IGameEvent* pEvent, uint32_t uHash, CTFPlayer* pLocal) for (int n = 1; n <= I::EngineClient->GetMaxClients(); n++) { PlayerInfo_t pi{}; - if (n == pLocal->entindex() || !I::EngineClient->GetPlayerInfo(n, &pi) || pi.fakeplayer) + if (n == pLocal->entindex() || !I::EngineClient->GetPlayerInfo(n, &pi) || pi.fakeplayer + || H::Entities.InParty(pi.friendsID)) // ignore party continue; TagsOnJoin(pi.name, pi.friendsID); diff --git a/Amalgam/src/Features/Simulation/MovementSimulation/MovementSimulation.cpp b/Amalgam/src/Features/Simulation/MovementSimulation/MovementSimulation.cpp index 44255621..47f52c2a 100644 --- a/Amalgam/src/Features/Simulation/MovementSimulation/MovementSimulation.cpp +++ b/Amalgam/src/Features/Simulation/MovementSimulation/MovementSimulation.cpp @@ -617,13 +617,8 @@ void CMovementSimulation::GetAverageYaw(PlayerStorage& playerStorage, int iSampl float flLowMinimumSamples = bGround ? Vars::Aimbot::Projectile::GroundLowMinimumSamples.Value : Vars::Aimbot::Projectile::AirLowMinimumSamples.Value; float flHighMinimumDistance = bGround ? Vars::Aimbot::Projectile::GroundHighMinimumDistance.Value : Vars::Aimbot::Projectile::AirHighMinimumDistance.Value; float flHighMinimumSamples = bGround ? Vars::Aimbot::Projectile::GroundHighMinimumSamples.Value : Vars::Aimbot::Projectile::AirHighMinimumSamples.Value; - float flNewWeight = (bGround ? Vars::Aimbot::Projectile::GroundNewWeight.Value : Vars::Aimbot::Projectile::AirNewWeight.Value) / 100; - float flOldWeight = (bGround ? Vars::Aimbot::Projectile::GroundOldWeight.Value : Vars::Aimbot::Projectile::AirOldWeight.Value) / 100; - float flMaxWeight = std::max(flNewWeight, flOldWeight); - if (!flMaxWeight) - return; - float flAverageYaw = 0.f, flAverageDelta = 0.f, flTotalWeight = 0.f; + float flAverageYaw = 0.f; int iTicks = 0, iSkips = 0, iDeltaChanges = 0; for (; iTicks < iSamples; iTicks++) { @@ -647,51 +642,22 @@ void CMovementSimulation::GetAverageYaw(PlayerStorage& playerStorage, int iSampl } */ - float flWeight = powf(Math::RemapValClamped(iTicks, 0, iSamples, flNewWeight, flOldWeight) / flMaxWeight, flMaxWeight); - flYaw *= flWeight / flMult, flTotalWeight += flWeight; - flAverageYaw += flYaw; - if (Vars::Aimbot::Projectile::StrafeDelta.Value) - { - static float flStaticYaw; - float flLastYaw = flStaticYaw; - flStaticYaw = flYaw; - if (iTicks > 0) - { - static float flStaticDelta; - float flLastDelta = flStaticDelta; - float flDelta = flStaticDelta = flLastYaw - flYaw; - if (iTicks > 1 && sign(flDelta) != sign(flLastDelta)) - iDeltaChanges++; - - flAverageDelta += flDelta; - } - } } iTicks -= iSkips; - if (!flTotalWeight || iTicks <= 3) // valid strafes not high enough + if (iTicks <= 3) // valid strafes not high enough return; - if (iTicks > 6) // valid strafes probably fine for this - flAverageDelta /= std::max(iDeltaChanges, 1); - else - flAverageDelta = 0.f; float flDistance = 0.f; if (auto pLocal = H::Entities.GetLocal()) flDistance = pLocal->m_vecOrigin().DistTo(playerStorage.m_pPlayer->m_vecOrigin()); int iMinimumStrafes = flDistance < flLowMinimumDistance ? flLowMinimumSamples : Math::RemapValClamped(flDistance, flLowMinimumDistance, flHighMinimumDistance, flLowMinimumSamples + 1, flHighMinimumSamples); - flAverageYaw /= flTotalWeight; - flAverageYaw *= std::min(float(iTicks) / iMinimumStrafes, 1.f); - flAverageDelta = std::clamp(flAverageDelta / std::max(iTicks, iMinimumStrafes), -0.05f, 0.05f); - if (fabsf(flAverageYaw) < 0.1f) - return; - - SDK::Output("MovementSimulation", std::format("flAverageYaw calculated to {} ({}) from {} ({}) {}", flAverageYaw, flAverageDelta, iTicks, iMinimumStrafes, pPlayer->entindex() == I::EngineClient->GetLocalPlayer() ? "(local)" : "").c_str(), {100, 255, 150, 255}, Vars::Debug::Logging.Value); - + flAverageYaw /= std::max(iTicks, iMinimumStrafes); playerStorage.m_flAverageYaw = flAverageYaw; - playerStorage.m_flAverageDelta = flAverageDelta; + + SDK::Output("MovementSimulation", std::format("flAverageYaw calculated to {} from {} ({}) {}", flAverageYaw, iTicks, iMinimumStrafes, pPlayer->entindex() == I::EngineClient->GetLocalPlayer() ? "(local)" : "").c_str(), {100, 255, 150, 255}, Vars::Debug::Logging.Value); } bool CMovementSimulation::StrafePrediction(PlayerStorage& playerStorage, int iSamples) @@ -721,7 +687,7 @@ void CMovementSimulation::RunTick(PlayerStorage& playerStorage, bool bPath) float flCorrection = 0.f; if (playerStorage.m_flAverageYaw) { - float flYaw = playerStorage.m_flAverageYaw, flDelta = playerStorage.m_flAverageDelta; + float flYaw = playerStorage.m_flAverageYaw; float flMult = 1.f; if (!playerStorage.m_pPlayer->IsOnGround()) @@ -733,9 +699,6 @@ void CMovementSimulation::RunTick(PlayerStorage& playerStorage, bool bPath) flMult = 0.f < flPrevVelZ && flPrevVelZ <= 250.f ? 0.25f : 1.f; // flEntFriction will be 0.25f if flPrevVelZ is between 0.f and 250.f } playerStorage.m_MoveData.m_vecViewAngles.y += flYaw * flMult + flCorrection; - - //playerStorage.m_flAverageYaw = std::clamp(flYaw + flDelta* flMult, sign(flYaw) == -1 ? flYaw : 0, sign(flYaw) == 1 ? flYaw : 0); - playerStorage.m_flAverageYaw = sign(flYaw) == -1 ? std::min(flYaw + flDelta * flMult, 0.f) : std::max(flYaw + flDelta * flMult, 0.f); } else { @@ -756,7 +719,7 @@ void CMovementSimulation::RunTick(PlayerStorage& playerStorage, bool bPath) } */ - float flOldSpeed = playerStorage.m_MoveData.m_flClientMaxSpeed, flOldSide = playerStorage.m_MoveData.m_flSideMove; + float flOldSpeed = playerStorage.m_MoveData.m_flClientMaxSpeed; if (playerStorage.m_PlayerData.m_fFlags & FL_DUCKING && playerStorage.m_pPlayer->IsOnGround()) playerStorage.m_MoveData.m_flClientMaxSpeed /= 3; diff --git a/Amalgam/src/Features/Simulation/MovementSimulation/MovementSimulation.h b/Amalgam/src/Features/Simulation/MovementSimulation/MovementSimulation.h index 924e91ed..96b7cf10 100644 --- a/Amalgam/src/Features/Simulation/MovementSimulation/MovementSimulation.h +++ b/Amalgam/src/Features/Simulation/MovementSimulation/MovementSimulation.h @@ -59,7 +59,6 @@ struct PlayerStorage PlayerData m_PlayerData = {}; float m_flAverageYaw = 0.f; - float m_flAverageDelta = 0.f; bool m_bBunnyHop = false; float m_flSimTime = 0.f; diff --git a/Amalgam/src/Features/Visuals/Materials/Materials.h b/Amalgam/src/Features/Visuals/Materials/Materials.h index b4a13af2..ac77bafc 100644 --- a/Amalgam/src/Features/Visuals/Materials/Materials.h +++ b/Amalgam/src/Features/Visuals/Materials/Materials.h @@ -1,7 +1,7 @@ #pragma once #include "../../../SDK/SDK.h" -#define MaterialFolder (F::Configs.sConfigPath + "\\Materials") +#define MaterialFolder (F::Configs.m_sConfigPath + "\\Materials") struct Material_t { diff --git a/Amalgam/src/Hooks/Direct3DDevice9.cpp b/Amalgam/src/Hooks/Direct3DDevice9.cpp index 4c65eb36..327f8972 100644 --- a/Amalgam/src/Hooks/Direct3DDevice9.cpp +++ b/Amalgam/src/Hooks/Direct3DDevice9.cpp @@ -50,10 +50,10 @@ MAKE_HOOK(Direct3DDevice9_Reset, U::Memory.GetVFunc(I::DirectXDevice, 16), HRESU LONG __stdcall WndProc::Func(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - if (F::Menu.IsOpen) + if (F::Menu.m_bIsOpen) { ImGui_ImplWin32_WndProcHandler(hWnd, uMsg, wParam, lParam); - if (ImGui::GetIO().WantTextInput || F::Menu.InKeybind) + if (ImGui::GetIO().WantTextInput || F::Menu.m_bInKeybind) { I::InputSystem->ResetInputState(); return 1; @@ -69,7 +69,7 @@ LONG __stdcall WndProc::Func(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) MAKE_HOOK(VGuiSurface_LockCursor, U::Memory.GetVFunc(I::MatSystemSurface, 62), void, void* rcx) { - if (F::Menu.IsOpen) + if (F::Menu.m_bIsOpen) return I::MatSystemSurface->UnlockCursor(); CALL_ORIGINAL(rcx); @@ -78,7 +78,7 @@ MAKE_HOOK(VGuiSurface_LockCursor, U::Memory.GetVFunc(I::MatSystemSurface, 62), v MAKE_HOOK(VGuiSurface_SetCursor, U::Memory.GetVFunc(I::MatSystemSurface, 51), void, void* rcx, HCursor cursor) { - if (F::Menu.IsOpen) + if (F::Menu.m_bIsOpen) { switch (F::Render.Cursor) { diff --git a/Amalgam/src/Hooks/HostState_Shutdown.cpp b/Amalgam/src/Hooks/HostState_Shutdown.cpp new file mode 100644 index 00000000..cd1e27d5 --- /dev/null +++ b/Amalgam/src/Hooks/HostState_Shutdown.cpp @@ -0,0 +1,20 @@ +#include "../SDK/SDK.h" + +#include "../Core/Core.h" + +MAKE_SIGNATURE(HostState_Shutdown, "engine.dll", "48 83 EC ? 80 3D ? ? ? ? ? 74 ? 48 8B 0D ? ? ? ? 48 85 C9 74 ? 48 8B 01 FF 50 ? 84 C0", 0x0); +MAKE_SIGNATURE(HostState_Restart, "engine.dll", "C7 05 ? ? ? ? ? ? ? ? C3 CC CC CC CC CC 48 83 EC", 0x0); + +MAKE_HOOK(HostState_Shutdown, S::HostState_Shutdown(), void, + ) +{ + U::Core.m_bUnload = true; + CALL_ORIGINAL(); +} + +MAKE_HOOK(HostState_Restart, S::HostState_Restart(), void, + ) +{ + U::Core.m_bUnload = true; + CALL_ORIGINAL(); +} \ No newline at end of file diff --git a/Amalgam/src/Hooks/IBaseClientDLL_DispatchUserMessage.cpp b/Amalgam/src/Hooks/IBaseClientDLL_DispatchUserMessage.cpp index 0a4fdbe7..85e2d5c7 100644 --- a/Amalgam/src/Hooks/IBaseClientDLL_DispatchUserMessage.cpp +++ b/Amalgam/src/Hooks/IBaseClientDLL_DispatchUserMessage.cpp @@ -7,8 +7,12 @@ #include "../Features/Aimbot/AutoHeal/AutoHeal.h" #include #include -//#include -//#include + +//#define DEBUG_VISUALS +#ifdef DEBUG_VISUALS +#include +#include +#endif MAKE_HOOK(IBaseClientDLL_DispatchUserMessage, U::Memory.GetVFunc(I::BaseClientDLL, 36), bool, void* rcx, UserMessageType type, bf_read& msgData) @@ -46,7 +50,7 @@ MAKE_HOOK(IBaseClientDLL_DispatchUserMessage, U::Memory.GetVFunc(I::BaseClientDL if (F::NoSpreadHitscan.ParsePlayerPerf(sMsg)) return true; - /* +#ifdef DEBUG_VISUALS if (sMsg.find("[BoxAngles] ") == 0) { try @@ -91,7 +95,7 @@ MAKE_HOOK(IBaseClientDLL_DispatchUserMessage, U::Memory.GetVFunc(I::BaseClientDL return true; } - */ +#endif if (Vars::Misc::Automation::AntiAutobalance.Value && FNV1A::Hash32(sMsg.c_str()) == FNV1A::Hash32Const("#TF_Autobalance_TeamChangePending")) I::EngineClient->ClientCmd_Unrestricted("retry"); diff --git a/Amalgam/src/SDK/SDK.cpp b/Amalgam/src/SDK/SDK.cpp index ec38ad6e..5d3c479c 100644 --- a/Amalgam/src/SDK/SDK.cpp +++ b/Amalgam/src/SDK/SDK.cpp @@ -6,7 +6,7 @@ #pragma warning (disable : 6385) -BOOL CALLBACK TeamFortressWindow(HWND hwnd, LPARAM lParam) +static BOOL CALLBACK TeamFortressWindow(HWND hwnd, LPARAM lParam) { char windowTitle[1024]; GetWindowTextA(hwnd, windowTitle, sizeof(windowTitle)); diff --git a/Amalgam/src/SDK/Vars.h b/Amalgam/src/SDK/Vars.h index 99ece502..a0ea1f27 100644 --- a/Amalgam/src/SDK/Vars.h +++ b/Amalgam/src/SDK/Vars.h @@ -108,8 +108,8 @@ namespace Vars CVar(CheapText, false) SUBNAMESPACE_BEGIN(Theme) - CVar(Accent, Color_t(255, 101, 101, 255), VISUAL) - CVar(Background, Color_t(23, 23, 23, 250), VISUAL) + CVar(Accent, Color_t(175, 150, 255, 255), VISUAL) + CVar(Background, Color_t(0, 0, 0, 250), VISUAL) CVar(Inactive, Color_t(150, 150, 150, 255), VISUAL) CVar(Active, Color_t(255, 255, 255, 255), VISUAL) SUBNAMESPACE_END(Theme) @@ -166,8 +166,6 @@ namespace Vars CVar(GroundHighMinimumDistance, 2500.f, DEBUGVAR) CVar(GroundMaxChanges, 0, DEBUGVAR) CVar(GroundMaxChangeTime, 0, DEBUGVAR) - CVar(GroundNewWeight, 100.f, DEBUGVAR) - CVar(GroundOldWeight, 100.f, DEBUGVAR) CVar(AirSamples, 33, DEBUGVAR) CVar(AirStraightFuzzyValue, 10.f, DEBUGVAR) @@ -177,13 +175,9 @@ namespace Vars CVar(AirHighMinimumDistance, 100000.f, DEBUGVAR) CVar(AirMaxChanges, 1, DEBUGVAR) CVar(AirMaxChangeTime, 10, DEBUGVAR) - CVar(AirNewWeight, 100.f, DEBUGVAR) - CVar(AirOldWeight, 100.f, DEBUGVAR) CVar(VelocityAverageCount, 5, DEBUGVAR) CVar(VerticalShift, 5.f, DEBUGVAR) - CVar(LatencyOffset, 0.f, DEBUGVAR) - CVar(HullIncrease, 0.f, DEBUGVAR) CVar(DragOverride, 0.f, DEBUGVAR) CVar(TimeOverride, 0.f, DEBUGVAR) CVar(HuntsmanLerp, 50.f, DEBUGVAR) @@ -199,7 +193,6 @@ namespace Vars CVar(SplashTraceInterval, 10, DEBUGVAR) CVar(DeltaCount, 5, DEBUGVAR) CVarEnum(DeltaMode, 0, DEBUGVAR, Average, Max) - CVar(StrafeDelta, false, DEBUGVAR) SUBNAMESPACE_END(Projectile) SUBNAMESPACE_BEGIN(Melee) @@ -835,7 +828,7 @@ namespace Vars CVar(BoundHitboxFace, Color_t(255, 255, 255, 0), VISUAL) CVar(BoundHitboxFaceClipped, Color_t(255, 255, 255, 0), VISUAL) - CVar(SpellFootstep, Color_t(255, 255, 255, 0), VISUAL) + CVar(SpellFootstep, Color_t(255, 255, 255, 255), VISUAL) NAMESPACE_END(Colors) NAMESPACE_BEGIN(Logging) diff --git a/Amalgam/src/Utils/CrashLog/CrashLog.cpp b/Amalgam/src/Utils/CrashLog/CrashLog.cpp index 8ee280eb..8c57438c 100644 --- a/Amalgam/src/Utils/CrashLog/CrashLog.cpp +++ b/Amalgam/src/Utils/CrashLog/CrashLog.cpp @@ -148,7 +148,7 @@ static LONG APIENTRY ExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) ssErrorStream << "\n\n\n\n"; std::ofstream file; - file.open(F::Configs.sConfigPath + "\\crash_log.txt", std::ios_base::app); + file.open(F::Configs.m_sConfigPath + "\\crash_log.txt", std::ios_base::app); file << ssErrorStream.str(); file.close(); diff --git a/Amalgam/src/Utils/KeyHandler/KeyHandler.cpp b/Amalgam/src/Utils/KeyHandler/KeyHandler.cpp index 6740a3da..0c57849f 100644 --- a/Amalgam/src/Utils/KeyHandler/KeyHandler.cpp +++ b/Amalgam/src/Utils/KeyHandler/KeyHandler.cpp @@ -6,69 +6,69 @@ void CKeyHandler::StoreKey(int iKey, KeyStorage* pStorage) { if (!pStorage) - pStorage = &StorageMap[iKey]; + pStorage = &m_mKeyStorage[iKey]; // down const bool bDown = iKey && GetAsyncKeyState(iKey) & 0x8000 && SDK::IsGameWindowInFocus(); // pressed - const bool bPressed = bDown && !pStorage->bIsDown; + const bool bPressed = bDown && !pStorage->m_bIsDown; // double click const double dFloatTime = SDK::PlatFloatTime(); - const bool bDouble = bPressed && dFloatTime < pStorage->dPressTime + 0.25; + const bool bDouble = bPressed && dFloatTime < pStorage->m_dPressTime + 0.25; // released - const bool bReleased = !bDown && pStorage->bIsDown; + const bool bReleased = !bDown && pStorage->m_bIsDown; - pStorage->bIsDown = bDown; - pStorage->bIsPressed = bPressed; - pStorage->bIsDouble = bDouble; - pStorage->bIsReleased = bReleased; + pStorage->m_bIsDown = bDown; + pStorage->m_bIsPressed = bPressed; + pStorage->m_bIsDouble = bDouble; + pStorage->m_bIsReleased = bReleased; if (bPressed) - pStorage->dPressTime = dFloatTime; + pStorage->m_dPressTime = dFloatTime; } bool CKeyHandler::Down(int iKey, const bool bStore, KeyStorage* pStorage) { if (!pStorage) - pStorage = &StorageMap[iKey]; + pStorage = &m_mKeyStorage[iKey]; if (bStore) StoreKey(iKey, pStorage); - return pStorage->bIsDown; + return pStorage->m_bIsDown; } bool CKeyHandler::Pressed(int iKey, const bool bStore, KeyStorage* pStorage) { if (!pStorage) - pStorage = &StorageMap[iKey]; + pStorage = &m_mKeyStorage[iKey]; if (bStore) StoreKey(iKey, pStorage); - return pStorage->bIsPressed; + return pStorage->m_bIsPressed; } bool CKeyHandler::Double(int iKey, const bool bStore, KeyStorage* pStorage) { if (!pStorage) - pStorage = &StorageMap[iKey]; + pStorage = &m_mKeyStorage[iKey]; if (bStore) StoreKey(iKey, pStorage); - return pStorage->bIsDouble; + return pStorage->m_bIsDouble; } bool CKeyHandler::Released(int iKey, const bool bStore, KeyStorage* pStorage) { if (!pStorage) - pStorage = &StorageMap[iKey]; + pStorage = &m_mKeyStorage[iKey]; if (bStore) StoreKey(iKey, pStorage); - return pStorage->bIsReleased; + return pStorage->m_bIsReleased; } \ No newline at end of file diff --git a/Amalgam/src/Utils/KeyHandler/KeyHandler.h b/Amalgam/src/Utils/KeyHandler/KeyHandler.h index 5e7e4acc..0b0f5581 100644 --- a/Amalgam/src/Utils/KeyHandler/KeyHandler.h +++ b/Amalgam/src/Utils/KeyHandler/KeyHandler.h @@ -4,17 +4,15 @@ struct KeyStorage { - bool bIsDown = false; - bool bIsPressed = false; - bool bIsDouble = false; - bool bIsReleased = false; - double dPressTime = 0; + bool m_bIsDown = false; + bool m_bIsPressed = false; + bool m_bIsDouble = false; + bool m_bIsReleased = false; + double m_dPressTime = 0; }; class CKeyHandler { - std::unordered_map StorageMap; - public: void StoreKey(int iKey, KeyStorage* pStorage = nullptr); @@ -29,6 +27,8 @@ public: // Was the button just released? This will only be true once. bool Released(int iKey, const bool bStore = false, KeyStorage* pStorage = nullptr); + + std::unordered_map m_mKeyStorage; }; ADD_FEATURE_CUSTOM(CKeyHandler, KeyHandler, U); \ No newline at end of file