Crafter.Graphics/implementations/Crafter.Graphics-InputField.cpp

175 lines
6.1 KiB
C++
Raw Normal View History

2026-05-03 02:45:38 +02:00
/*
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;
2026-05-12 00:24:48 +02:00
import :Keys;
2026-05-03 02:45:38 +02:00
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();
}
2026-05-12 00:24:48 +02:00
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();
2026-05-03 02:45:38 +02:00
}
}
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<float>(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<float>(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,
};
}
}