175 lines
6.1 KiB
C++
175 lines
6.1 KiB
C++
/*
|
|
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<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,
|
|
};
|
|
}
|
|
}
|