This commit is contained in:
Jorijn van der Graaf 2026-05-03 02:45:38 +02:00
commit c054f1e0b3
9 changed files with 699 additions and 5 deletions

View file

@ -0,0 +1,180 @@
/*
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 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, CrafterKeys key) {
switch (key) {
case CrafterKeys::Backspace:
if (f.cursorPos > 0 && !f.value.empty()) {
f.value.erase(f.cursorPos - 1, 1);
--f.cursorPos;
}
break;
case CrafterKeys::Delete:
if (f.cursorPos < f.value.size()) {
f.value.erase(f.cursorPos, 1);
}
break;
case CrafterKeys::Left:
if (f.cursorPos > 0) --f.cursorPos;
break;
case CrafterKeys::Right:
if (f.cursorPos < f.value.size()) ++f.cursorPos;
break;
case CrafterKeys::Home:
f.cursorPos = 0;
break;
case CrafterKeys::End:
f.cursorPos = f.value.size();
break;
default: break;
}
}
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,
};
}
}

View file

@ -184,3 +184,50 @@ void Crafter::DrawProgressBar(UIBuffer& buf, Rect r, float t01,
});
}
}
// ─── DrawText ───────────────────────────────────────────────────────────
float Crafter::DrawText(UIBuffer& buf, std::string_view text,
float x, float baselineY,
Font& font, float fontSize,
std::array<float, 4> color,
TextAlign align)
{
if (text.empty() || buf.atlas == nullptr || buf.renderer == nullptr) return 0.0f;
if (buf.glyphs == nullptr || buf.glyphCount == nullptr) return 0.0f;
std::uint32_t before = *buf.glyphCount;
std::uint32_t cap = (buf.glyphCap > before) ? (buf.glyphCap - before) : 0;
if (cap == 0) return 0.0f;
GlyphItem* writePos = buf.glyphs + before;
float advance = 0.0f;
std::uint32_t n = buf.renderer->ShapeText(
font, fontSize, x, baselineY,
text, color, writePos, cap, &advance
);
*buf.glyphCount = before + n;
if (align != TextAlign::Left && n > 0) {
float shift = (align == TextAlign::Center) ? -advance * 0.5f : -advance;
for (std::uint32_t i = 0; i < n; ++i) writePos[i].x += shift;
}
return advance;
}
// ─── DrawImage ──────────────────────────────────────────────────────────
void Crafter::DrawImage(UIBuffer& buf, Rect r,
std::uint32_t textureSlot, std::uint32_t samplerSlot,
std::array<float, 4> tint,
std::array<float, 4> uv)
{
if (buf.images == nullptr || buf.imageCount == nullptr) return;
if (*buf.imageCount >= buf.imageCap) return;
buf.images[(*buf.imageCount)++] = ImageItem{
r.x, r.y, r.w, r.h,
uv[0], uv[1], uv[2], uv[3],
tint[0], tint[1], tint[2], tint[3],
textureSlot, samplerSlot, 0, 0,
};
}