/* Crafter®.Graphics Copyright (C) 2026 Catcrafts® catcrafts.net This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 3.0 as published by the Free Software Foundation; This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ module; module Crafter.Graphics:InputField_impl; import :InputField; import :UI; import :UIComponents; import :Font; import :Types; import :Keys; import std; using namespace Crafter; bool Crafter::InputField_IsValidCandidate(InputFieldType type, std::string_view s) { switch (type) { case InputFieldType::Text: return true; case InputFieldType::UInt: { for (char c : s) if (c < '0' || c > '9') return false; return true; } case InputFieldType::Int: { if (s.empty()) return true; std::size_t i = 0; if (s[0] == '-') { if (s.size() == 1) return true; // lone '-' allowed mid-edit i = 1; } for (; i < s.size(); ++i) if (s[i] < '0' || s[i] > '9') return false; return true; } case InputFieldType::Float: { if (s.empty()) return true; std::size_t i = 0; if (s[0] == '-') { if (s.size() == 1) return true; i = 1; } bool sawDigit = false, sawDot = false, sawExp = false; for (; i < s.size(); ++i) { char c = s[i]; if (c >= '0' && c <= '9') { if (!sawExp) sawDigit = true; } else if (c == '.') { if (sawDot || sawExp) return false; sawDot = true; } else if (c == 'e' || c == 'E') { if (sawExp || !sawDigit) return false; sawExp = true; if (i + 1 < s.size() && (s[i + 1] == '+' || s[i + 1] == '-')) ++i; } else { return false; } } return true; } } return true; } void Crafter::InputField_OnText(InputField& f, std::string_view utf8) { if (utf8.empty()) return; std::string candidate = f.value; candidate.insert(f.cursorPos, utf8); if (!InputField_IsValidCandidate(f.type, candidate)) return; f.value = std::move(candidate); f.cursorPos += utf8.size(); } void Crafter::InputField_OnKey(InputField& f, KeyCode code) { // `Key(...)` is consteval — every comparison below folds to an immediate // integer at compile time, so this is just a sequence of int-eq tests. if (code == Key(CrafterKeys::Backspace)) { if (f.cursorPos > 0 && !f.value.empty()) { f.value.erase(f.cursorPos - 1, 1); --f.cursorPos; } } else if (code == Key(CrafterKeys::Delete)) { if (f.cursorPos < f.value.size()) { f.value.erase(f.cursorPos, 1); } } else if (code == Key(CrafterKeys::Left)) { if (f.cursorPos > 0) --f.cursorPos; } else if (code == Key(CrafterKeys::Right)) { if (f.cursorPos < f.value.size()) ++f.cursorPos; } else if (code == Key(CrafterKeys::Home)) { f.cursorPos = 0; } else if (code == Key(CrafterKeys::End)) { f.cursorPos = f.value.size(); } } std::size_t Crafter::InputField_HitTestCursor(const InputField& f, Rect rect, float clickX, Font& font, float fontSize, const InputFieldColors& colors) { float target = clickX - (rect.x + colors.paddingX); if (target <= 0.0f) return 0; std::size_t best = 0; float bestDist = std::abs(target); for (std::size_t i = 1; i <= f.value.size(); ++i) { std::string_view sub(f.value.data(), i); float w = static_cast(font.GetLineWidth(sub, fontSize)); float d = std::abs(target - w); if (d < bestDist) { bestDist = d; best = i; } else { break; } } return best; } void Crafter::DrawInputField(UIBuffer& buf, const InputField& f, Rect rect, Font& font, float fontSize, const InputFieldColors& c, bool caretVisible) { if (buf.quads == nullptr || buf.quadCount == nullptr) return; const auto& bg = f.focused ? c.bgFocused : c.bg; const auto& border = f.focused ? c.borderFocused : c.border; if (*buf.quadCount < buf.quadCap) { buf.quads[(*buf.quadCount)++] = QuadItem{ rect.x, rect.y, rect.w, rect.h, bg[0], bg[1], bg[2], bg[3], c.cornerRadius, c.cornerRadius, c.cornerRadius, c.cornerRadius, c.borderThickness, border[0], border[1], border[2], }; } float baseline = rect.y + rect.h * 0.5f + fontSize * 0.32f; float textX = rect.x + c.paddingX; DrawText(buf, f.value, textX, baseline, font, fontSize, c.text, TextAlign::Left); if (f.focused && caretVisible && *buf.quadCount < buf.quadCap) { std::string_view sub(f.value.data(), std::min(f.cursorPos, f.value.size())); float caretX = textX + (sub.empty() ? 0.0f : static_cast(font.GetLineWidth(sub, fontSize))); float caretH = fontSize * 1.1f; float caretY = rect.y + (rect.h - caretH) * 0.5f; float caretW = std::max(1.0f, fontSize / 16.0f); buf.quads[(*buf.quadCount)++] = QuadItem{ caretX, caretY, caretW, caretH, c.caret[0], c.caret[1], c.caret[2], c.caret[3], 0, 0, 0, 0, 0, 0, 0, 0, }; } }