text控件:新增光标后插入;新增可控边界
This commit is contained in:
@@ -44,6 +44,45 @@ namespace ezui {
|
||||
__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)
|
||||
{
|
||||
//需要屏蔽
|
||||
@@ -81,35 +120,29 @@ namespace ezui {
|
||||
point2 = m_textLayout->HitTestTextPosition(m_A_TextPos, m_A_isTrailingHit);
|
||||
point1 = m_textLayout->HitTestTextPosition(m_B_TextPos, m_B_isTrailingHit);
|
||||
}
|
||||
// 中心偏移用于 hit test 保证在行内
|
||||
float offsetY = m_textLayout->GetFontHeight() / 2.0f;
|
||||
point1.Y += offsetY;
|
||||
point2.Y += offsetY;
|
||||
|
||||
// 获取每行矩形
|
||||
// 为了稳定行判定,对基线 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) {
|
||||
// 首行
|
||||
if (point1.Y >= lr.Y && point1.Y <= lr.Y + lr.Height) {
|
||||
if (point2.Y <= lr.Y + lr.Height) {
|
||||
// 同一行
|
||||
float width = std::max(0.0f, float(point2.X - point1.X));
|
||||
m_selectRects.push_back(Rect(point1.X, lr.Y, width, lr.Height));
|
||||
}
|
||||
else {
|
||||
// 跨行首行 从 point1 到行末
|
||||
float width = std::max(0.0f, lr.X + lr.Width - point1.X);
|
||||
m_selectRects.push_back(Rect(point1.X, lr.Y, width, lr.Height));
|
||||
}
|
||||
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 (point2.Y >= lr.Y && point2.Y <= lr.Y + 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 (point1.Y < lr.Y && point2.Y > lr.Y + lr.Height) {
|
||||
else if (sampleY1 < lr.Y && sampleY2 >= lr.Y + lr.Height) { // 中间整行
|
||||
m_selectRects.push_back(lr);
|
||||
}
|
||||
}
|
||||
@@ -166,12 +199,13 @@ namespace ezui {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void TextBox::InsertUnicode(const std::wstring& str) {
|
||||
void TextBox::InsertUnicode(const std::wstring& str, bool isEnd) {
|
||||
DeleteRange();//先删除是否有选中的区域
|
||||
if (m_textPos < 0)m_textPos = 0;
|
||||
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) {
|
||||
@@ -336,35 +370,45 @@ namespace ezui {
|
||||
*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_multiLine) {//单行编辑框
|
||||
m_font->Get()->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
|
||||
bool bAlignLeft = (int(this->TextAlign) & int(HAlign::Left));
|
||||
float width = bAlignLeft ? EZUI_FLOAT_MAX : Width();
|
||||
m_textLayout = new TextLayout(*drawText, *m_font, SizeF{ width,(float)Height() }, this->TextAlign);
|
||||
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 < this->Width()) {
|
||||
if (m_fontBox.Width < innerW) {
|
||||
m_scrollX = 0;
|
||||
}
|
||||
if (!bAlignLeft && m_fontBox.Width > this->Width()) {
|
||||
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 > this->Width() && m_scrollX + m_fontBox.Width < this->Width()) {
|
||||
m_scrollX = this->Width() - m_fontBox.Width;
|
||||
if (m_fontBox.Width > innerW && m_scrollX + m_fontBox.Width < innerW) {
|
||||
m_scrollX = innerW - m_fontBox.Width;
|
||||
}
|
||||
}
|
||||
else {//多行编辑框
|
||||
m_font->Get()->SetWordWrapping(DWRITE_WORD_WRAPPING_WRAP);
|
||||
m_textLayout = new TextLayout(*drawText, *m_font, SizeF{ (float)Width(),EZUI_FLOAT_MAX }, TextAlign::TopLeft);
|
||||
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;
|
||||
}
|
||||
this->SetContentSize({ m_fontBox.Width , m_fontBox.Height });
|
||||
// 内容尺寸:多行时加入 padding 以保证最后一行不被裁掉,单行保持原逻辑
|
||||
if (m_multiLine) {
|
||||
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_multiLine) {
|
||||
this->GetScrollBar()->RefreshScroll();
|
||||
}
|
||||
@@ -388,14 +432,26 @@ namespace ezui {
|
||||
m_careRect.Width = 1 * this->GetScale();
|
||||
|
||||
if (!m_multiLine) {
|
||||
//使光标一直在输入框内
|
||||
int drawX = m_careRect.X + m_scrollX;
|
||||
if (drawX < 0) {//光标在最左侧
|
||||
m_scrollX -= drawX;
|
||||
// 使光标保持在可视内区(考虑 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 (drawX > Width()) {//光标在最右侧
|
||||
int ofssetX = (Width() - drawX);
|
||||
m_scrollX += ofssetX;
|
||||
if (caretDrawX > innerW) { // 右越界
|
||||
int offsetX = innerW - caretDrawX;
|
||||
m_scrollX += offsetX;
|
||||
}
|
||||
}
|
||||
else { // 多行:处理垂直滚动保持光标可见
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,18 +488,19 @@ namespace ezui {
|
||||
{
|
||||
__super::OnMouseWheel(arg);
|
||||
if (!m_multiLine) {//单行
|
||||
int innerW = std::max(0, Width() - (m_padLeft + m_padRight));
|
||||
int textWidth = m_fontBox.Width;
|
||||
if (arg.ZDelta > 0 && textWidth > 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>Width()) {
|
||||
else if (arg.ZDelta<0 && textWidth>innerW) {
|
||||
m_scrollX -= std::abs(arg.ZDelta) * 0.5;
|
||||
if (-m_scrollX + Width() > textWidth) {
|
||||
m_scrollX = -(textWidth - Width());
|
||||
if (-m_scrollX + innerW > textWidth) {
|
||||
m_scrollX = -(textWidth - innerW);
|
||||
}
|
||||
Invalidate();
|
||||
}
|
||||
@@ -479,9 +536,10 @@ namespace ezui {
|
||||
}
|
||||
|
||||
Point TextBox::ConvertPoint(const Point& pt) {
|
||||
int _x = -m_scrollX;
|
||||
int _y = -m_scrollY;
|
||||
return Point{ pt.X + _x,pt.Y + _y };
|
||||
// 将控件坐标转换为文本布局坐标:去掉滚动与内边距偏移
|
||||
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)
|
||||
@@ -498,23 +556,24 @@ namespace ezui {
|
||||
BuildSelectedRect();
|
||||
|
||||
if (!m_multiLine) {//单行
|
||||
//当鼠标往左侧移动
|
||||
// 计算去掉左 padding 的鼠标相对文本区域坐标
|
||||
int innerW = std::max(0, Width() - (m_padLeft + m_padRight));
|
||||
int textWidth = m_fontBox.Width;
|
||||
if (m_lastX > point.X) {
|
||||
int localX = point.X - m_padLeft; // 鼠标在文本可编辑区域内的 X
|
||||
if (m_lastX > point.X) { // 向左拖
|
||||
m_lastX = point.X;
|
||||
if (textWidth > Width() && m_scrollX < 0 && point.X < 0) {
|
||||
if (textWidth > innerW && m_scrollX < 0 && localX < 0) {
|
||||
m_scrollX += 3;
|
||||
Invalidate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
//当鼠标往右侧移动
|
||||
if (m_lastX < point.X) {
|
||||
if (m_lastX < point.X) { // 向右拖
|
||||
m_lastX = point.X;
|
||||
if (textWidth > Width() && point.X > Width()) {
|
||||
if (textWidth > innerW && localX > innerW) {
|
||||
m_scrollX -= 3;
|
||||
if (-m_scrollX + Width() > textWidth) {
|
||||
m_scrollX = -(textWidth - Width());
|
||||
if (-m_scrollX + innerW > textWidth) {
|
||||
m_scrollX = -(textWidth - innerW);
|
||||
}
|
||||
Invalidate();
|
||||
return;
|
||||
@@ -593,19 +652,24 @@ namespace ezui {
|
||||
Rect TextBox::GetCareRect()
|
||||
{
|
||||
Rect rect(m_careRect);
|
||||
rect.X += m_scrollX;//偏移
|
||||
rect.Y += m_scrollY;
|
||||
rect.X += m_scrollX + m_padLeft; // 滚动 + padding
|
||||
rect.Y += m_scrollY + m_padTop;
|
||||
return rect;
|
||||
}
|
||||
void TextBox::Insert(const UIString& str)
|
||||
void TextBox::Insert(const UIString& str, bool isEnd)
|
||||
{
|
||||
InsertUnicode(str.unicode());
|
||||
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);
|
||||
@@ -687,8 +751,9 @@ namespace ezui {
|
||||
if ((!bAlignCenter) || (bAlignCenter && !m_focus)) {//避免光标和placeholder重叠
|
||||
Color placeholderColor = fontColor;
|
||||
placeholderColor.SetA(fontColor.GetA() * 0.6);
|
||||
e.Graphics.SetColor(placeholderColor);
|
||||
e.Graphics.DrawString(m_placeholder, RectF(0, 0, (float)Width(), (float)Height()), m_multiLine ? TextAlign::TopLeft : this->TextAlign);
|
||||
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));
|
||||
e.Graphics.DrawString(m_placeholder, ph, m_multiLine ? TextAlign::TopLeft : this->TextAlign);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,8 +764,8 @@ namespace ezui {
|
||||
for (auto& it : m_selectRects) {
|
||||
if (!it.IsEmptyArea()) {
|
||||
RectF rect(it);
|
||||
rect.X += m_scrollX;//偏移
|
||||
rect.Y += m_scrollY;
|
||||
rect.X += m_scrollX + m_padLeft; // 加上内边距
|
||||
rect.Y += m_scrollY + m_padTop;
|
||||
e.Graphics.FillRectangle(rect);
|
||||
}
|
||||
}
|
||||
@@ -708,17 +773,17 @@ namespace ezui {
|
||||
|
||||
if (m_textLayout) {
|
||||
e.Graphics.SetColor(fontColor);
|
||||
e.Graphics.DrawTextLayout(*m_textLayout, PointF{ (float)m_scrollX, (float)m_scrollY });
|
||||
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_careRect.Y, m_careRect.Width, m_careRect.Height);
|
||||
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.X = this->Width() - rect.Width; //末尾微调
|
||||
}
|
||||
rect.Width = rect.Width * this->GetScale();
|
||||
e.Graphics.SetColor(fontColor);
|
||||
|
||||
Reference in New Issue
Block a user