Crafter.Graphics/interfaces/Crafter.Graphics-InputField.cppm
2026-05-03 02:45:38 +02:00

103 lines
4.3 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;
export module Crafter.Graphics:InputField;
import std;
import :Types;
import :Font;
import :UI;
import :UIComponents;
// Tier 3: single-line text input. Same contract as the other UI components —
// state is a user-owned POD, the draw function is stateless presentation, and
// input feeding is done via free functions called from the host's own event
// listeners. Validation rules cover the common numeric cases (UInt/Int/Float)
// and accept "in-progress" intermediate states (lone '-', '.', "1e") so the
// user can type naturally; final-form parsing is on the caller via TryParse.
export namespace Crafter {
enum class InputFieldType : std::uint8_t {
Text, // any UTF-8
UInt, // digits only
Int, // optional leading '-', then digits
Float, // signed float with optional exponent
};
struct InputFieldColors {
std::array<float, 4> bg;
std::array<float, 4> bgFocused;
std::array<float, 4> border;
std::array<float, 4> borderFocused;
std::array<float, 4> text;
std::array<float, 4> caret;
float cornerRadius = 6.0f;
float borderThickness = 2.0f;
float paddingX = 8.0f; // text inset from the rect's left edge
};
struct InputField {
std::string value;
InputFieldType type = InputFieldType::Text;
bool focused = false;
std::size_t cursorPos = 0; // byte offset into `value`
};
// Returns true if `s` is a valid candidate for the given type, including
// "in-progress" states (lone '-', empty, "1e", "1.").
bool InputField_IsValidCandidate(InputFieldType, std::string_view s);
// Try to parse the field's current value into T. Returns nullopt for empty
// or in-progress strings. Defined for arithmetic types via std::from_chars.
template <class T>
std::optional<T> InputField_TryParse(const InputField& f) {
if (f.value.empty()) return std::nullopt;
T out{};
const char* begin = f.value.data();
const char* end = begin + f.value.size();
auto [ptr, ec] = std::from_chars(begin, end, out);
if (ec != std::errc{} || ptr != end) return std::nullopt;
return out;
}
// Insert UTF-8 text at the cursor. The full would-be result is validated
// against the field's type before committing — invalid candidates are
// dropped silently. Cursor advances by the inserted byte count.
void InputField_OnText(InputField&, std::string_view utf8);
// Edit-control keys: Backspace, Delete, Left, Right, Home, End. Anything
// else is ignored. Safe to feed every key the host receives.
void InputField_OnKey(InputField&, CrafterKeys);
// Map a click x-coord (in window pixels) to a cursor position. `rect` is
// the field's current draw rect; `colors.paddingX` is consulted for the
// text-start offset.
std::size_t InputField_HitTestCursor(const InputField&,
Rect rect,
float clickX,
Font&, float fontSize,
const InputFieldColors&);
// Draw the field. `caretVisible` is provided by the caller so that blink
// policy stays in user code (typical: `(steady_clock::now() / 500ms) & 1`).
// The caret is only drawn if the field is focused AND caretVisible is true.
void DrawInputField(UIBuffer& buf, const InputField& f, Rect rect,
Font& font, float fontSize,
const InputFieldColors& colors,
bool caretVisible);
}