关键节点
This commit is contained in:
154
editor/include/XCEditor/Widgets/UIEditorTextLayout.h
Normal file
154
editor/include/XCEditor/Widgets/UIEditorTextLayout.h
Normal file
@@ -0,0 +1,154 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEditor/Foundation/UIEditorTextMeasurement.h>
|
||||
|
||||
#include <XCEngine/UI/Text/UITextEditing.h>
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
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 float ResolveUIEditorControlTextTop(
|
||||
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 + 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);
|
||||
}
|
||||
|
||||
inline float MeasureUIEditorTextWidth(
|
||||
const std::string& text,
|
||||
float fontSize,
|
||||
const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer = nullptr) {
|
||||
if (textMeasurer != nullptr &&
|
||||
!text.empty() &&
|
||||
fontSize > 0.0f) {
|
||||
return textMeasurer->MeasureTextWidth(
|
||||
::XCEngine::UI::Editor::UIEditorTextMeasureRequest{
|
||||
text,
|
||||
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,
|
||||
const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer = nullptr) {
|
||||
const std::size_t clampedCaretOffset = (std::min)(caretOffset, text.size());
|
||||
if (textMeasurer != nullptr &&
|
||||
clampedCaretOffset > 0u &&
|
||||
fontSize > 0.0f) {
|
||||
return textMeasurer->MeasureTextAdvance(
|
||||
::XCEngine::UI::Editor::UIEditorTextMeasureRequest{
|
||||
std::string_view(text.data(), clampedCaretOffset),
|
||||
fontSize
|
||||
});
|
||||
}
|
||||
|
||||
return fontSize * 0.56f *
|
||||
static_cast<float>(::XCEngine::UI::Text::CountUtf8CodepointsInRange(
|
||||
text,
|
||||
0u,
|
||||
clampedCaretOffset));
|
||||
}
|
||||
|
||||
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,
|
||||
const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer = nullptr) {
|
||||
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,
|
||||
textMeasurer));
|
||||
const float top = ResolveUIEditorControlTextTop(rect, fontSize, insetY);
|
||||
const float bottom = top + MeasureUIEditorTextLayoutHeight(fontSize);
|
||||
drawList.AddLine(
|
||||
::XCEngine::UI::UIPoint(caretX, top),
|
||||
::XCEngine::UI::UIPoint(caretX, bottom),
|
||||
color,
|
||||
thickness);
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Widgets
|
||||
Reference in New Issue
Block a user