2026-04-08 02:52:28 +08:00
|
|
|
#pragma once
|
|
|
|
|
|
2026-04-21 00:57:14 +08:00
|
|
|
#include <XCEngine/UI/Text/UITextEditing.h>
|
2026-04-08 02:52:28 +08:00
|
|
|
#include <XCEngine/UI/DrawData.h>
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
2026-04-21 00:57:14 +08:00
|
|
|
#include <chrono>
|
2026-04-08 02:52:28 +08:00
|
|
|
#include <cmath>
|
2026-04-21 00:57:14 +08:00
|
|
|
#include <string>
|
2026-04-08 02:52:28 +08:00
|
|
|
|
|
|
|
|
namespace XCEngine::UI::Editor::Widgets {
|
|
|
|
|
|
|
|
|
|
inline float MeasureUIEditorTextLayoutHeight(float fontSize) {
|
|
|
|
|
return (std::max)(0.0f, fontSize * 1.35f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline float ResolveUIEditorTextTop(
|
|
|
|
|
const ::XCEngine::UI::UIRect& rect,
|
|
|
|
|
float fontSize,
|
|
|
|
|
float offsetY = 0.0f) {
|
|
|
|
|
const float textHeight = MeasureUIEditorTextLayoutHeight(fontSize);
|
|
|
|
|
const float centeredTop =
|
|
|
|
|
rect.y + (std::max)(0.0f, std::floor((rect.height - textHeight) * 0.5f));
|
|
|
|
|
return centeredTop - 1.0f + offsetY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline ::XCEngine::UI::UIRect ResolveUIEditorTextClipRect(
|
|
|
|
|
const ::XCEngine::UI::UIRect& rect,
|
|
|
|
|
float fontSize) {
|
|
|
|
|
const float extraPadding = (std::max)(2.0f, std::ceil(fontSize * 0.35f));
|
|
|
|
|
return ::XCEngine::UI::UIRect(
|
|
|
|
|
rect.x,
|
|
|
|
|
rect.y - 1.0f,
|
|
|
|
|
rect.width,
|
|
|
|
|
rect.height + extraPadding + 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 00:57:14 +08:00
|
|
|
inline float MeasureUIEditorTextWidth(
|
|
|
|
|
const std::string& text,
|
|
|
|
|
float fontSize) {
|
|
|
|
|
return fontSize * 0.56f *
|
|
|
|
|
static_cast<float>(::XCEngine::UI::Text::CountUtf8Codepoints(text));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline float MeasureUIEditorTextWidthToCaret(
|
|
|
|
|
const std::string& text,
|
|
|
|
|
std::size_t caretOffset,
|
|
|
|
|
float fontSize) {
|
|
|
|
|
return fontSize * 0.56f *
|
|
|
|
|
static_cast<float>(::XCEngine::UI::Text::CountUtf8CodepointsInRange(
|
|
|
|
|
text,
|
|
|
|
|
0u,
|
|
|
|
|
(std::min)(caretOffset, text.size())));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline std::uint64_t GetUIEditorTextCaretClockNanoseconds() {
|
|
|
|
|
using Clock = std::chrono::steady_clock;
|
|
|
|
|
const auto now = Clock::now().time_since_epoch();
|
|
|
|
|
return static_cast<std::uint64_t>(
|
|
|
|
|
std::chrono::duration_cast<std::chrono::nanoseconds>(now).count());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool ShouldBlinkUIEditorTextCaret(std::uint64_t blinkStartNanoseconds) {
|
|
|
|
|
constexpr std::uint64_t kInitialVisibleDurationNanoseconds = 500000000ull;
|
|
|
|
|
constexpr std::uint64_t kBlinkPhaseDurationNanoseconds = 500000000ull;
|
|
|
|
|
|
|
|
|
|
if (blinkStartNanoseconds == 0u) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::uint64_t nowNanoseconds = GetUIEditorTextCaretClockNanoseconds();
|
|
|
|
|
if (nowNanoseconds <= blinkStartNanoseconds) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::uint64_t elapsedNanoseconds = nowNanoseconds - blinkStartNanoseconds;
|
|
|
|
|
if (elapsedNanoseconds < kInitialVisibleDurationNanoseconds) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::uint64_t blinkElapsedNanoseconds =
|
|
|
|
|
elapsedNanoseconds - kInitialVisibleDurationNanoseconds;
|
|
|
|
|
return ((blinkElapsedNanoseconds / kBlinkPhaseDurationNanoseconds) % 2u) == 0u;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void AppendUIEditorTextCaret(
|
|
|
|
|
::XCEngine::UI::UIDrawList& drawList,
|
|
|
|
|
const ::XCEngine::UI::UIRect& rect,
|
|
|
|
|
const std::string& text,
|
|
|
|
|
std::size_t caretOffset,
|
|
|
|
|
std::uint64_t blinkStartNanoseconds,
|
|
|
|
|
const ::XCEngine::UI::UIColor& color,
|
|
|
|
|
float fontSize,
|
|
|
|
|
float insetX,
|
|
|
|
|
float insetY = 0.0f,
|
|
|
|
|
float thickness = 1.0f) {
|
|
|
|
|
if (color.a <= 0.0f || !ShouldBlinkUIEditorTextCaret(blinkStartNanoseconds)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float caretX = (std::min)(
|
|
|
|
|
rect.x + rect.width - 1.0f,
|
|
|
|
|
rect.x + insetX + MeasureUIEditorTextWidthToCaret(text, caretOffset, fontSize));
|
|
|
|
|
const float top = ResolveUIEditorTextTop(rect, fontSize, insetY);
|
|
|
|
|
const float bottom = top + MeasureUIEditorTextLayoutHeight(fontSize);
|
|
|
|
|
drawList.AddLine(
|
|
|
|
|
::XCEngine::UI::UIPoint(caretX, top),
|
|
|
|
|
::XCEngine::UI::UIPoint(caretX, bottom),
|
|
|
|
|
color,
|
|
|
|
|
thickness);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 02:52:28 +08:00
|
|
|
} // namespace XCEngine::UI::Editor::Widgets
|