Files
EzUI/sources/TextBox.cpp
2026-02-20 15:02:30 +08:00

841 lines
24 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "TextBox.h"
#undef min
#undef max
namespace ezui {
TextBox::TextBox(Object* parentObject) :Control(parentObject)
{
Init();
}
TextBox::~TextBox() {
m_timer->Stop();
if (m_textLayout) { delete m_textLayout; }
if (m_font) { delete m_font; }
}
void TextBox::Init()
{
this->GetScrollBar()->SetWidth(5);
this->GetScrollBar()->Parent = this;
this->GetScrollBar()->OffsetCallback = [=](int offset) {
this->Offset(offset);
};
Style.Cursor = LoadCursor(Cursor::IBEAM);
m_timer = new Timer(this);
m_timer->Interval = 500;
m_timer->Tick = [this](Timer* t) {
HWND hWnd = this->Hwnd();//在对象析构前获取句柄
BeginInvoke([this, hWnd]() {//捕获hWnd
if (!::IsWindow(hWnd))return;//如果窗口已经销毁 则不往下执行
if (!this->IsEnabled() || this->IsReadOnly()) {
m_bCareShow = false;
return;
}
if (!m_careRect.IsEmptyArea() && m_focus) {
m_bCareShow = !m_bCareShow;
this->Invalidate();
}
});
};
}
void TextBox::OnRemove() {
__super::OnRemove();
m_timer->Stop();
}
void TextBox::SetPadding(int px, int py) {
// 兼容旧接口:水平=px 垂直=py
SetPadding4(px, py, px, py);
}
void TextBox::SetPadding4(int left, int top, int right, int bottom) {
left = std::max(0, left); top = std::max(0, top); right = std::max(0, right); bottom = std::max(0, bottom);
if (m_padLeft != left || m_padTop != top || m_padRight != right || m_padBottom != bottom) {
m_padLeft = left; m_padTop = top; m_padRight = right; m_padBottom = bottom;
Analysis();
Invalidate();
}
}
bool TextBox::ApplyStyleProperty(const UIString& key, const UIString& value) {
// 先让基类尝试
if (__super::ApplyStyleProperty(key, value)) return true;
auto parseInt = [](const UIString& v)->int {
UIString tmp = v; ui_text::Replace(&tmp, "px", ""); tmp = tmp.trim();
return std::atoi(tmp.c_str());
};
// 新增:四边独立 & 兼容旧属性
if (key == "padding-left") { SetPadding4(parseInt(value), m_padTop, m_padRight, m_padBottom); return true; }
if (key == "padding-right") { SetPadding4(m_padLeft, m_padTop, parseInt(value), m_padBottom); return true; }
if (key == "padding-top") { SetPadding4(m_padLeft, parseInt(value), m_padRight, m_padBottom); return true; }
if (key == "padding-bottom") { SetPadding4(m_padLeft, m_padTop, m_padRight, parseInt(value)); return true; }
if (key == "padding-x") { int p = parseInt(value); SetPadding4(p, m_padTop, p, m_padBottom); return true; }
if (key == "padding-y") { int p = parseInt(value); SetPadding4(m_padLeft, p, m_padRight, p); return true; }
if (key == "padding") {
UIString v = value; ui_text::Replace(&v, " ", ","); auto arr = v.split(",");
if (arr.size() == 1) { int p = parseInt(arr[0]); SetPadding4(p, p, p, p); return true; }
if (arr.size() == 2) { // 兼容旧语义:水平 垂直
int px = parseInt(arr[0]); int py = parseInt(arr[1]); SetPadding4(px, py, px, py); return true; }
if (arr.size() == 3) { // CSS: top horizontal bottom
int pTop = parseInt(arr[0]); int pH = parseInt(arr[1]); int pBottom = parseInt(arr[2]); SetPadding4(pH, pTop, pH, pBottom); return true; }
if (arr.size() == 4) { // top right bottom left
int pTop = parseInt(arr[0]); int pRight = parseInt(arr[1]); int pBottom = parseInt(arr[2]); int pLeft = parseInt(arr[3]); SetPadding4(pLeft, pTop, pRight, pBottom); return true; }
return true; // 不合法也拦截
}
return false;
}
void TextBox::SetAutoWidth(bool flag)
{
//需要屏蔽
}
void TextBox::SetAutoHeight(bool flag)
{
//需要屏蔽
}
void TextBox::OnKeyChar(const KeyboardEventArgs& arg)
{
__super::OnKeyChar(arg);
WPARAM wParam = arg.wParam;
LPARAM lParam = arg.lParam;
if (wParam < 32)return;//控制字符
if (IsReadOnly()) return;//只读
WCHAR buf[2]{ (WCHAR)wParam ,0 };//
InsertUnicode(std::wstring(buf));//插入新的字符
Analysis();//分析字符串
Invalidate();//刷新
}
#undef max
#undef min
void TextBox::BuildSelectedRect() {
m_selectRects.clear();
if (m_textLayout) {
//获取起点和终点
Point point1, point2;
if ((m_A_TextPos + m_A_isTrailingHit) < (m_B_TextPos + m_B_isTrailingHit)) {
point1 = m_textLayout->HitTestTextPosition(m_A_TextPos, m_A_isTrailingHit);
point2 = m_textLayout->HitTestTextPosition(m_B_TextPos, m_B_isTrailingHit);
}
else {
point2 = m_textLayout->HitTestTextPosition(m_A_TextPos, m_A_isTrailingHit);
point1 = m_textLayout->HitTestTextPosition(m_B_TextPos, m_B_isTrailingHit);
}
// 为了稳定行判定,对基线 Y 加一个行高一半偏移放到行中部
float sampleY1 = point1.Y + m_textLayout->GetFontHeight() / 2.0f;
float sampleY2 = point2.Y + m_textLayout->GetFontHeight() / 2.0f;
auto lineRects = m_textLayout->GetLineRects();
for (auto& lr : lineRects) {
bool firstLine = (sampleY1 >= lr.Y && sampleY1 < lr.Y + lr.Height + 0.5f);
bool lastLine = (sampleY2 >= lr.Y && sampleY2 < lr.Y + lr.Height + 0.5f);
if (firstLine && lastLine) { // 同一行
float leftX = point1.X;
float rightX = point2.X;
if (rightX < leftX) std::swap(leftX, rightX);
float width = std::max(0.0f, rightX - leftX);
m_selectRects.push_back(Rect(leftX, lr.Y, width, lr.Height));
}
else if (firstLine) { // 首行:到行尾
float width = std::max(0.0f, (lr.X + lr.Width) - point1.X);
m_selectRects.push_back(Rect(point1.X, lr.Y, width, lr.Height));
}
else if (lastLine) { // 末行:从行首
float width = std::max(0.0f, point2.X - lr.X);
m_selectRects.push_back(Rect(lr.X, lr.Y, width, lr.Height));
}
else if (sampleY1 < lr.Y && sampleY2 >= lr.Y + lr.Height) { // 中间整行
m_selectRects.push_back(lr);
}
}
}
}
bool TextBox::SelectedAll() {
if (m_textLayout && !m_text.empty()) {
m_pointA = Point{ 0,0 };
m_A_isTrailingHit = FALSE;
m_A_TextPos = 0;
m_pointB = Point{ m_fontBox.Width ,0 };
m_B_isTrailingHit = TRUE;
m_B_TextPos = m_text.size() - 1;
BuildSelectedRect();
return true;
}
return false;
}
bool TextBox::GetSelectedRange(int* outPos, int* outCount) {
if (m_selectRects.size() > 0) {
int pos, count;
if ((m_A_TextPos + m_A_isTrailingHit) < (m_B_TextPos + m_B_isTrailingHit)) {
int pos1 = m_A_TextPos;
if (m_A_isTrailingHit == 1) {
pos1 += 1;
}
int pos2 = m_B_TextPos;
if (m_B_isTrailingHit == 0) {
pos2 -= 1;
}
pos = pos1;
count = std::abs(pos2 - pos1) + 1;
}
else {
int pos1 = m_A_TextPos;
if (m_A_isTrailingHit == 0) {
pos1 -= 1;
}
int pos2 = m_B_TextPos;
if (m_B_isTrailingHit == 1) {
pos2 += 1;
}
pos = pos2;
count = std::abs(pos2 - pos1) + 1;
}
*outPos = pos;
*outCount = count;
if (*outCount > 0) {
return true;
}
}
return false;
}
void TextBox::InsertUnicode(const std::wstring& str, bool isEnd) {
DeleteRange();//先删除是否有选中的区域
if (m_textPos < 0) m_textPos = 0; // 处理光标位置
if (m_textPos > (int)m_text.size()) {
m_textPos = m_text.size();
}
if (isEnd) m_textPos = m_text.size(); // 是否让光标移到末尾
m_text.insert(m_textPos, str);
m_textPos += str.size();
if (TextChanged) {
TextChanged(UIString(m_text));
}
}
bool TextBox::DeleteRange() {
int pos, count;
if (GetSelectedRange(&pos, &count)) {//删除选中的
//isTrailingHit = FALSE;
m_textPos = pos;
m_text.erase(pos, count);
if (TextChanged) {
TextChanged(UIString(m_text));
}
return true;
}
return false;
}
bool TextBox::Copy() {
int pos, count;
if (!GetSelectedRange(&pos, &count))return false;
std::wstring wBuf(m_text.substr(pos, count));
return ezui::CopyToClipboard(wBuf, Hwnd());
}
bool TextBox::Paste() {
std::wstring wBuf;
bool bRet = ezui::GetClipboardData(&wBuf, Hwnd());
UIString u8Str(wBuf);
if (!m_allowManualLineBreak) {
//不允许手动换行时不允许有换行符
ui_text::Replace(&u8Str, "\r", "");
ui_text::Replace(&u8Str, "\n", "");
}
InsertUnicode(u8Str.unicode());//插入新的字符
return bRet;
}
void TextBox::OnBackspace() {
if (m_text.size() <= 0)return;
if (!DeleteRange()) {//先看看有没有有选中的需要删除
//否则删除单个字符
m_textPos--;
if (m_textPos > -1) {
m_text.erase(m_textPos, 1);
if (TextChanged) {
TextChanged(UIString(m_text));
}
}
}
}
void TextBox::OnKeyDown(const KeyboardEventArgs& arg)
{
__super::OnKeyDown(arg);
WPARAM wParam = arg.wParam;
LPARAM lParam = arg.lParam;
//判断是否按下shift+enter仅当允许手动换行时
if (m_allowManualLineBreak && (GetKeyState(VK_SHIFT) & 0x8000) && wParam == VK_RETURN) {
InsertUnicode(L"\n");//插入换行符
Analysis();//分析字符串
Invalidate();//刷新
return;
}
//判断是否按下Ctrl键
if (GetKeyState(VK_CONTROL) & 0x8000) {
do
{
if (wParam == 'A') {//ctrl+a全选
SelectedAll();//全选
Invalidate();//刷新
break;
}
if (wParam == 'C') {//ctrl+c 复制
Copy();
break;
}
if (wParam == 'X') {//ctrl+x 剪切
if (IsReadOnly()) {
break;
}
if (Copy()) {
DeleteRange();//因为是剪切 所以要删除选中的这段
Analysis();
Invalidate();//刷新
}
break;
}
if (wParam == 'V') {//ctrl+v 粘贴
if (IsReadOnly()) {
break;
}
Paste();
Analysis();//分析字符串
Invalidate();//刷新
break;
}
if (wParam == 'Z') {//ctrl+z撤销
if (IsReadOnly()) {
break;
}
break;
}
} while (false);
return;
}
if (wParam == VK_BACK) { //退格键(删除字符)
if (IsReadOnly()) {
return;
}
OnBackspace();//退格键的操作在里面
Analysis();//重新分析
Invalidate();//刷新
return;
}
if (wParam == VK_LEFT) {
m_textPos--;
m_bCareShow = true;
m_selectRects.clear();
BuildCare();
Invalidate();
return;
}
if (wParam == VK_RIGHT) {
++m_textPos;
m_bCareShow = true;
m_selectRects.clear();
BuildCare();
Invalidate();
return;
}
}
void TextBox::Analysis()
{
if (m_text.size() > (size_t)(this->m_maxLen)) {
m_text.erase((size_t)(this->m_maxLen));
}
m_scrollX = 0;
m_scrollY = 0;
m_pointA = Point();
m_A_isTrailingHit = 0;
m_A_TextPos = 0;
m_pointB = Point();
m_B_isTrailingHit = 0;
m_B_TextPos = 0;
m_careRect = Rect();
m_selectRects.clear();
if (m_font == NULL) return;
if (m_textLayout) delete m_textLayout;
std::wstring* drawText = &this->m_text;
if (!m_passwordChar.empty()) {
drawText = new std::wstring;
int count = m_passwordChar.size() * m_text.size();
for (size_t i = 0; i < m_text.size(); ++i)
{
*drawText += m_passwordChar;
}
}
else {
*drawText = m_text;
}
// 可用绘制宽高(考虑内边距)
int innerW = std::max(0, Width() - (m_padLeft + m_padRight));
int innerH = std::max(0, Height() - (m_padTop + m_padBottom));
if (!m_autoWrap) {//不自动换行时需要水平拖动滚动编辑框(两者都不允许)
m_font->Get()->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
bool bAlignLeft = (int(this->TextAlign) & int(HAlign::Left));
float width = bAlignLeft ? EZUI_FLOAT_MAX : (float)innerW;
m_textLayout = new TextLayout(*drawText, *m_font, SizeF{ width,(float)innerH }, this->TextAlign);
m_fontBox = m_textLayout->GetFontBox();
if (m_fontBox.Width < innerW) {
m_scrollX = 0;
}
if (!bAlignLeft && m_fontBox.Width > innerW) {
ezui::TextAlign tmp = this->TextAlign;
tmp = ezui::TextAlign((int)tmp & ~(int)HAlign::Center);
tmp = ezui::TextAlign((int)tmp & ~(int)HAlign::Right);
tmp = ezui::TextAlign((int)tmp | (int)HAlign::Left);
m_textLayout->SetTextAlign(tmp);
}
if (m_fontBox.Width > innerW && m_scrollX + m_fontBox.Width < innerW) {
m_scrollX = innerW - m_fontBox.Width;
}
}
else {//多行编辑框(至少允许一种换行方式)
// 根据 m_autoWrap 决定是否启用自动换行
if (m_autoWrap) {
m_font->Get()->SetWordWrapping(DWRITE_WORD_WRAPPING_WRAP);
} else {
m_font->Get()->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
}
m_textLayout = new TextLayout(*drawText, *m_font, SizeF{ (float)innerW, EZUI_FLOAT_MAX }, TextAlign::TopLeft);
m_fontBox = m_textLayout->GetFontBox();
}
if (drawText != &this->m_text) {
delete drawText;
}
// 内容尺寸:多行时加入 padding 以保证最后一行不被裁掉,单行保持原逻辑
if (m_autoWrap || m_allowManualLineBreak) {
this->SetContentSize({ m_fontBox.Width + m_padLeft + m_padRight, m_fontBox.Height + m_padTop + m_padBottom });
}
else {
this->SetContentSize({ m_fontBox.Width , m_fontBox.Height });
}
if (m_autoWrap || m_allowManualLineBreak) {
this->GetScrollBar()->RefreshScroll();
}
BuildCare();
}
void TextBox::BuildCare() {
if (!m_textLayout) return;
if (m_textPos < 0) {
m_textPos = 0;
}
if (m_textPos > (int)m_text.size()) {
m_textPos = m_text.size();
}
Point pt = m_textLayout->HitTestTextPosition(m_textPos, FALSE);
m_careRect.X = pt.X;
m_careRect.Y = pt.Y;
m_careRect.Height = m_textLayout->GetFontHeight();
m_careRect.Width = 1 * this->GetScale();
// 处理水平滚动(当不自动换行时需要水平滚动)
if (!m_autoWrap) {
// 使光标保持在可视内区(考虑 padding
int innerW = std::max(0, Width() - (m_padLeft + m_padRight));
int caretDrawX = m_careRect.X + m_scrollX; // 布局坐标 + 滚动
if (caretDrawX < 0) { // 左越界
m_scrollX -= caretDrawX;
}
if (caretDrawX > innerW) { // 右越界
int offsetX = innerW - caretDrawX;
m_scrollX += offsetX;
}
}
// 处理垂直滚动(多行模式需要垂直滚动)
if (m_autoWrap || m_allowManualLineBreak) {
int innerH = std::max(0, Height() - (m_padTop + m_padBottom));
int caretDrawY = m_careRect.Y + m_scrollY; // 仅布局+滚动
if (caretDrawY < 0) {
m_scrollY -= caretDrawY;
}
int caretBottom = caretDrawY + m_careRect.Height;
if (caretBottom > innerH) {
m_scrollY -= (caretBottom - innerH);
}
}
}
void TextBox::OnMouseDown(const MouseEventArgs& arg) {
__super::OnMouseDown(arg);
m_lastX = 0;
m_lastY = 0;
auto mbtn = arg.Button;
auto point = arg.Location;
if (mbtn == MouseButton::Left) {
m_down = true;
m_point_Start = ConvertPoint(point);
if (m_textLayout) {
int fontHeight;
m_selectRects.clear();
m_pointA = m_textLayout->HitTestPoint(m_point_Start, &m_A_TextPos, &m_A_isTrailingHit, &fontHeight);
m_careRect.X = m_pointA.X;
m_careRect.Y = m_pointA.Y;
m_careRect.Width = 1;
m_careRect.Height = fontHeight;
m_textPos = m_A_TextPos;
if (m_A_isTrailingHit) {
++m_textPos;
}
}
Invalidate();
}
}
void TextBox::OnMouseWheel(const MouseEventArgs& arg)
{
__super::OnMouseWheel(arg);
if (!m_autoWrap) {//不自动换行时需要水平拖动滚动
int innerW = std::max(0, Width() - (m_padLeft + m_padRight));
int textWidth = m_fontBox.Width;
if (arg.ZDelta > 0 && textWidth > innerW) {
m_scrollX += std::abs(arg.ZDelta) * 0.5;
if (m_scrollX > 0) {
m_scrollX = 0;
}
Invalidate();
}
else if (arg.ZDelta<0 && textWidth>innerW) {
m_scrollX -= std::abs(arg.ZDelta) * 0.5;
if (-m_scrollX + innerW > textWidth) {
m_scrollX = -(textWidth - innerW);
}
Invalidate();
}
}
}
ScrollBar* TextBox::GetScrollBar()
{
return &m_vScrollbar;
}
void TextBox::Offset(int _sliderY) {
this->m_scrollY = _sliderY;
Invalidate();
}
void TextBox::OnLayout()
{
__super::OnLayout();
m_scrollX = 0;
m_scrollY = 0;
m_selectRects.clear();
if ((!m_autoWrap && !m_allowManualLineBreak) && Height() != m_lastHeight) {
m_lastHeight = Height();
Analysis();
}
if ((m_autoWrap || m_allowManualLineBreak) && Width() != m_lastWidth) {
m_lastWidth = Width();
Analysis();
}
this->SetContentSize({ m_fontBox.Width ,(m_autoWrap || m_allowManualLineBreak) ? m_fontBox.Height : Height() });
this->GetScrollBar()->RefreshScroll();
this->EndLayout();
}
Point TextBox::ConvertPoint(const Point& pt) {
// 将控件坐标转换为文本布局坐标:去掉滚动与内边距偏移
int _x = -m_scrollX - m_padLeft;
int _y = -m_scrollY - m_padTop;
return Point{ pt.X + _x, pt.Y + _y };
}
void TextBox::OnMouseMove(const MouseEventArgs& arg)
{
__super::OnMouseMove(arg);
auto point = arg.Location;
if (m_down) {
m_point_End = ConvertPoint(point);
if (m_textLayout) {
int fontHeight;
m_selectRects.clear();//
m_pointB = m_textLayout->HitTestPoint(m_point_End, &m_B_TextPos, &m_B_isTrailingHit, &fontHeight);
BuildSelectedRect();
if (!m_autoWrap) {//不自动换行时需要水平拖动滚动
// 计算去掉左 padding 的鼠标相对文本区域坐标
int innerW = std::max(0, Width() - (m_padLeft + m_padRight));
int textWidth = m_fontBox.Width;
int localX = point.X - m_padLeft; // 鼠标在文本可编辑区域内的 X
if (m_lastX > point.X) { // 向左拖
m_lastX = point.X;
if (textWidth > innerW && m_scrollX < 0 && localX < 0) {
m_scrollX += 3;
Invalidate();
return;
}
}
if (m_lastX < point.X) { // 向右拖
m_lastX = point.X;
if (textWidth > innerW && localX > innerW) {
m_scrollX -= 3;
if (-m_scrollX + innerW > textWidth) {
m_scrollX = -(textWidth - innerW);
}
Invalidate();
return;
}
}
}
Invalidate();
}
}
}
void TextBox::OnMouseUp(const MouseEventArgs& arg)
{
__super::OnMouseUp(arg);
m_down = false;
m_lastX = 0;
m_lastY = 0;
Invalidate();
}
void TextBox::OnFocus(const FocusEventArgs& arg)
{
__super::OnFocus(arg);
m_focus = true;
m_bCareShow = true;
m_timer->Start();
Invalidate();
}
void TextBox::OnKillFocus(const KillFocusEventArgs& arg)
{
__super::OnKillFocus(arg);
m_down = false;
m_focus = false;
m_bCareShow = false;
m_timer->Stop();
this->Invalidate();
}
const UIString TextBox::GetText()
{
return UIString(this->m_text);
}
void TextBox::SetText(const UIString& text)
{
this->m_text = text.unicode();
Analysis();
}
bool TextBox::IsMultiLine()
{
return m_autoWrap || m_allowManualLineBreak;
}
void TextBox::SetMultiLine(bool autoWrap, bool allowManualLineBreak)
{
if (this->m_autoWrap != autoWrap || this->m_allowManualLineBreak != allowManualLineBreak) {
this->m_autoWrap = autoWrap;
this->m_allowManualLineBreak = allowManualLineBreak;
Analysis();
}
}
void TextBox::SetReadOnly(bool bReadOnly)
{
this->m_readOnly = bReadOnly;
}
bool TextBox::IsReadOnly()
{
return this->m_readOnly;
}
void TextBox::SetMaxLength(int maxLen)
{
this->m_maxLen = maxLen;
}
void TextBox::SetPlaceholderText(const UIString& text)
{
this->m_placeholder = text.unicode();
}
void TextBox::SetPasswordChar(const UIString& passwordChar)
{
this->m_passwordChar = passwordChar.unicode();
}
void TextBox::SetFocusAndCaret(int caretPos)
{
// 设置焦点状态
m_focus = true;
m_bCareShow = true;
m_timer->Start();
// 设置光标位置
if (caretPos < 0) {
m_textPos = (int)m_text.size(); // -1 表示末尾
} else {
m_textPos = caretPos;
}
// 确保 m_textPos 在有效范围内
if (m_textPos < 0) m_textPos = 0;
if (m_textPos > (int)m_text.size()) m_textPos = (int)m_text.size();
// 清除选中状态
m_selectRects.clear();
m_pointA = Point();
m_pointB = Point();
m_A_TextPos = m_textPos;
m_B_TextPos = m_textPos;
// 需要重绘以确保 m_font 和 m_textLayout 被初始化
// 之后 OnForePaint 中会调用 BuildCare
Invalidate();
}
Rect TextBox::GetCareRect()
{
Rect rect(m_careRect);
rect.X += m_scrollX + m_padLeft; // 滚动 + padding
rect.Y += m_scrollY + m_padTop;
return rect;
}
void TextBox::Insert(const UIString& str, bool isEnd)
{
InsertUnicode(str.unicode(), isEnd);
Analysis();//分析字符串
}
void TextBox::SetAttribute(const UIString& key, const UIString& value) {
__super::SetAttribute(key, value);
do
{
if (key == "padding" || key == "padding-x" || key == "padding-y") {
// 走统一样式解析,直接调用 ApplyStyleProperty
ApplyStyleProperty(key, value);
break;
}
if (key == "valign") {
this->TextAlign = ezui::TextAlign((int)this->TextAlign & ~(int)VAlign::Top);
this->TextAlign = ezui::TextAlign((int)this->TextAlign & ~(int)VAlign::Mid);
this->TextAlign = ezui::TextAlign((int)this->TextAlign & ~(int)VAlign::Bottom);
VAlign v = VAlign::Mid;
if (value == "top") {
v = VAlign::Top;
}
else if (value == "bottom") {
v = VAlign::Bottom;
}
this->TextAlign = ezui::TextAlign((int)this->TextAlign | (int)v);
break;
}
if (key == "halign") {
this->TextAlign = ezui::TextAlign((int)this->TextAlign & ~(int)HAlign::Left);
this->TextAlign = ezui::TextAlign((int)this->TextAlign & ~(int)HAlign::Center);
this->TextAlign = ezui::TextAlign((int)this->TextAlign & ~(int)HAlign::Right);
HAlign h = HAlign::Center;
if (value == "left") {
h = HAlign::Left;
}
else if (value == "right") {
h = HAlign::Right;
}
this->TextAlign = ezui::TextAlign((int)this->TextAlign | (int)h);
break;
}
if (key == "passwordchar") {
this->SetPasswordChar(value);
break;
}
if (key == "placeholder") {
this->SetPlaceholderText(value);
break;
}
if (key == "text" || key == "value") {
this->SetText(value);
break;
}
if (key == "readonly") {
if (value == "true") {
this->SetReadOnly(true);
break;
}
if (value == "false") {
this->SetReadOnly(false);
break;
}
}
if (key == "multiline") {
if (value == "true") {
// 兼容旧版multiline=true 表示不自动换行+手动换行
this->m_autoWrap = false;
this->m_allowManualLineBreak = true;
break;
}
if (value == "false") {
this->m_autoWrap = false;
this->m_allowManualLineBreak = false;
break;
}
}
} while (false);
}
void TextBox::OnForePaint(PaintEventArgs& e) {
std::wstring fontFamily = GetFontFamily();
auto fontSize = GetFontSize();
if (fontSize == 0)return;
if (m_font == NULL || ((m_font != NULL) && (m_font->GetFontFamily() != fontFamily || m_font->GetFontSize() != fontSize))) {
if (m_font != NULL) {
delete m_font;
}
m_font = new Font(fontFamily, fontSize);
Analysis();
}
// 如果有焦点但光标未初始化,重新构建光标
if (m_focus && m_careRect.IsEmptyArea() && m_textLayout) {
BuildCare();
}
Color fontColor = GetForeColor();
e.Graphics.SetFont(fontFamily, fontSize);
if (m_text.empty()) {
bool bAlignCenter = (int(this->TextAlign) & int(HAlign::Center));
if ((!bAlignCenter) || (bAlignCenter && !m_focus)) {//避免光标和placeholder重叠
Color placeholderColor = fontColor;
placeholderColor.SetA(fontColor.GetA() * 0.6);
e.Graphics.SetColor(placeholderColor);
RectF ph((float)m_padLeft, (float)m_padTop, (float)Width() - (float)(m_padLeft + m_padRight), (float)Height() - (float)(m_padTop + m_padBottom));
Font phFont(fontFamily, fontSize);
e.Graphics.SetFont(phFont);
e.Graphics.DrawString(m_placeholder, ph, (m_autoWrap || m_allowManualLineBreak) ? TextAlign::TopLeft : this->TextAlign);
}
}
if (m_selectRects.size() > 0) {
Color selectedColor = fontColor;
selectedColor.SetA(fontColor.GetA() * 0.35);
e.Graphics.SetColor(selectedColor);
for (auto& it : m_selectRects) {
if (!it.IsEmptyArea()) {
RectF rect(it);
rect.X += m_scrollX + m_padLeft; // 加上内边距
rect.Y += m_scrollY + m_padTop;
e.Graphics.FillRectangle(rect);
}
}
}
if (m_textLayout) {
e.Graphics.SetColor(fontColor);
PointF base{ (float)m_scrollX + (float)m_padLeft, (float)m_scrollY + (float)m_padTop };
e.Graphics.DrawTextLayout(*m_textLayout, base);
}
if (!m_careRect.IsEmptyArea() && m_focus) {
if (m_bCareShow) {
RectF rect(m_careRect.X + m_padLeft, m_careRect.Y + m_padTop, m_careRect.Width, m_careRect.Height);
rect.X += m_scrollX;//偏移
rect.Y += m_scrollY;
if (ezui::IsFloatEqual(rect.X, this->Width())) {
rect.X = this->Width() - rect.Width; //末尾微调
}
rect.Width = rect.Width * this->GetScale();
e.Graphics.SetColor(fontColor);
e.Graphics.FillRectangle(rect);
}
}
}
}