初版-带一个进度条

This commit is contained in:
睿 安
2026-01-24 22:42:46 +08:00
commit c367128889
100 changed files with 19726 additions and 0 deletions

729
sources/TextBox.cpp Normal file
View File

@@ -0,0 +1,729 @@
#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::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);
}
// 中心偏移用于 hit test 保证在行内
float offsetY = m_textLayout->GetFontHeight() / 2.0f;
point1.Y += offsetY;
point2.Y += offsetY;
// 获取每行矩形
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));
}
}
// 末行
else if (point2.Y >= lr.Y && point2.Y <= lr.Y + lr.Height) {
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) {
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) {
DeleteRange();//先删除是否有选中的区域
if (m_textPos < 0)m_textPos = 0;
if (m_textPos > (int)m_text.size()) {
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_multiLine) {
//行编辑框不允许有换行符
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 (IsMultiLine() && (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;
}
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);
m_fontBox = m_textLayout->GetFontBox();
if (m_fontBox.Width < this->Width()) {
m_scrollX = 0;
}
if (!bAlignLeft && m_fontBox.Width > this->Width()) {
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;
}
}
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_fontBox = m_textLayout->GetFontBox();
}
if (drawText != &this->m_text) {
delete drawText;
}
this->SetContentSize({ m_fontBox.Width , m_fontBox.Height });
if (m_multiLine) {
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_multiLine) {
//使光标一直在输入框内
int drawX = m_careRect.X + m_scrollX;
if (drawX < 0) {//光标在最左侧
m_scrollX -= drawX;
}
if (drawX > Width()) {//光标在最右侧
int ofssetX = (Width() - drawX);
m_scrollX += ofssetX;
}
}
}
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_multiLine) {//单行
int textWidth = m_fontBox.Width;
if (arg.ZDelta > 0 && textWidth > Width()) {
m_scrollX += std::abs(arg.ZDelta) * 0.5;
if (m_scrollX > 0) {
m_scrollX = 0;
}
Invalidate();
}
else if (arg.ZDelta<0 && textWidth>Width()) {
m_scrollX -= std::abs(arg.ZDelta) * 0.5;
if (-m_scrollX + Width() > textWidth) {
m_scrollX = -(textWidth - Width());
}
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_multiLine && Height() != m_lastHeight) {
m_lastHeight = Height();
Analysis();
}
if (m_multiLine && Width() != m_lastWidth) {
m_lastWidth = Width();
Analysis();
}
this->SetContentSize({ m_fontBox.Width ,m_multiLine ? m_fontBox.Height : Height() });
this->GetScrollBar()->RefreshScroll();
this->EndLayout();
}
Point TextBox::ConvertPoint(const Point& pt) {
int _x = -m_scrollX;
int _y = -m_scrollY;
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_multiLine) {//单行
//当鼠标往左侧移动
int textWidth = m_fontBox.Width;
if (m_lastX > point.X) {
m_lastX = point.X;
if (textWidth > Width() && m_scrollX < 0 && point.X < 0) {
m_scrollX += 3;
Invalidate();
return;
}
}
//当鼠标往右侧移动
if (m_lastX < point.X) {
m_lastX = point.X;
if (textWidth > Width() && point.X > Width()) {
m_scrollX -= 3;
if (-m_scrollX + Width() > textWidth) {
m_scrollX = -(textWidth - Width());
}
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_multiLine;
}
void TextBox::SetMultiLine(bool multiLine)
{
if (this->m_multiLine != multiLine) {
this->m_multiLine = multiLine;
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();
}
Rect TextBox::GetCareRect()
{
Rect rect(m_careRect);
rect.X += m_scrollX;//偏移
rect.Y += m_scrollY;
return rect;
}
void TextBox::Insert(const UIString& str)
{
InsertUnicode(str.unicode());
Analysis();//分析字符串
}
void TextBox::SetAttribute(const UIString& key, const UIString& value) {
__super::SetAttribute(key, value);
do
{
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") {
this->m_multiLine = true;
break;
}
if (value == "false") {
this->m_multiLine = 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();
}
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);
e.Graphics.DrawString(m_placeholder, RectF(0, 0, (float)Width(), (float)Height()), m_multiLine ? 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;//偏移
rect.Y += m_scrollY;
e.Graphics.FillRectangle(rect);
}
}
}
if (m_textLayout) {
e.Graphics.SetColor(fontColor);
e.Graphics.DrawTextLayout(*m_textLayout, PointF{ (float)m_scrollX, (float)m_scrollY });
}
if (!m_careRect.IsEmptyArea() && m_focus) {
if (m_bCareShow) {
RectF rect(m_careRect.X, m_careRect.Y, 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);
}
}
}
}