diff --git a/other/build.nr b/other/build.nr index c4970c4..671b223 100644 --- a/other/build.nr +++ b/other/build.nr @@ -1 +1 @@ -2090 +2091 diff --git a/src/ogfx/gui/widgets/Text.cpp b/src/ogfx/gui/widgets/Text.cpp index f072db2..9947953 100644 --- a/src/ogfx/gui/widgets/Text.cpp +++ b/src/ogfx/gui/widgets/Text.cpp @@ -86,6 +86,49 @@ namespace ogfx + bool TextEdit::HexadecimalFilter::isValidChar(const String& utf8, const ostd::TextBuffer& buffer) + { + // Reject anything that isn't a single ASCII byte. Multi-codepoint + // input (IME, emoji, accented chars) is never valid in an hex. + if (utf8.len() != 1) return false; + const char c = utf8[0]; + if (!isHexDigit(c)) return false; + + // Predict the post-insert string and validate it as a prefix. + const String result = predictResult(utf8, buffer); + return isValidHexPrefix(result); + } + + bool TextEdit::HexadecimalFilter::isValidHexPrefix(const String& s) const + { + if (s.empty()) return true; + const ostd::cpp_string& bytes = s.cpp_str(); + + u32 i = 0; + + // Everything from i onward must be digits. Count them while we're at it. + u32 digitCount = 0; + while (i < bytes.size()) { + if (!isHexDigit(bytes[i])) return false; + digitCount++; + i++; + } + + if (m_maxDigits > 0 && digitCount > cast(m_maxDigits)) + return false; + + return true; + } + + bool TextEdit::HexadecimalFilter::isHexDigit(char c) const + { + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); + } + + + + + bool TextEdit::DecimalFilter::isValidChar(const String& utf8, const ostd::TextBuffer& buffer) { if (utf8.len() != 1) return false; diff --git a/src/ogfx/gui/widgets/Text.hpp b/src/ogfx/gui/widgets/Text.hpp index 87a1b31..f5c28fd 100644 --- a/src/ogfx/gui/widgets/Text.hpp +++ b/src/ogfx/gui/widgets/Text.hpp @@ -52,6 +52,17 @@ namespace ogfx bool m_allowNegative; i32 m_maxDigits; }; + public: class HexadecimalFilter : public CharacterFilter + { + public: + inline HexadecimalFilter(i32 maxDigits = -1) : m_maxDigits(maxDigits) {} + bool isValidChar(const String& utf8, const ostd::TextBuffer& buffer) override; + private: + bool isValidHexPrefix(const String& s) const; + bool isHexDigit(char c) const; + private: + i32 m_maxDigits; + }; public: class DecimalFilter : public CharacterFilter { public: @@ -112,6 +123,7 @@ namespace ogfx inline void useCustomFilter(CharacterFilter* filter) { m_owned_filter.reset(); m_filter = filter; } inline void useDateFilter(DateFilter::eFormat format = DateFilter::eFormat::DDMMYYYY, char separator = '.') { use_filter(std::make_unique(format, separator)); } inline void useIntegerFilter(bool allowNegative = true, i32 maxDigits = -1) { use_filter(std::make_unique(allowNegative, maxDigits)); } + inline void useHexadecimalFilter(i32 maxDigits = -1) { use_filter(std::make_unique(maxDigits)); } inline void useDecimalFilter(bool allowNegative = true, char separator = '.', i32 maxIntegerDigits = -1, i32 maxFractionDigits = -1) { use_filter(std::make_unique(allowNegative, separator, maxIntegerDigits, maxFractionDigits)); } void setText(const String& text); diff --git a/src/test/GuiTest.cpp b/src/test/GuiTest.cpp index 9de89fb..3746c94 100644 --- a/src/test/GuiTest.cpp +++ b/src/test/GuiTest.cpp @@ -225,6 +225,7 @@ class TestWindow : public Window std::cout << previous.getText() << "\n"; std::cout << sender.getText() << "\n\n"; }); + m_text.useHexadecimalFilter(4); t1.addWidget(m_prog, { 30, 200 }); t1.addWidget(m_slide, { 30, 250 }); t1.addWidget(m_slideLbl, { 340, 240 });