text控件:新增光标后插入;新增可控边界

This commit is contained in:
睿 安
2026-01-27 14:45:32 +08:00
parent 1be1ecbbf2
commit 9fe2fe5874
29 changed files with 255 additions and 114 deletions

View File

@@ -44,4 +44,7 @@ function(add_resource_package TARGET_NAME INPUT_DIR OUTPUT_FILE)
endfunction() endfunction()
#添加子项目 #添加子项目
#add_subdirectory(./demo) # 暂时注释掉,只编译库 option(BUILD_DEMO "Build demo projects" ON)
if(BUILD_DEMO)
add_subdirectory(./demo)
endif()

Binary file not shown.

View File

@@ -7,7 +7,6 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
Application app(hInstance); Application app(hInstance);
app.EnableHighDpi();//启用高DPI app.EnableHighDpi();//启用高DPI
//创建登录创建 //创建登录创建
loginForm loginFrm; loginForm loginFrm;
loginFrm.Show(); loginFrm.Show();

Binary file not shown.

View File

@@ -1,2 +1,17 @@
//{{NO_DEPENDENCIES}} //{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file. // Microsoft Visual C++ 生成的包含文件。
// 供 DemoUi.rc 使用
//
#define IDD_INPUT 102
#define IDC_EDIT1 1000
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 104
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

View File

@@ -3,7 +3,6 @@
#include <windows.h> #include <windows.h>
#include "ezui/Application.h" //app类 #include "ezui/Application.h" //app类
#include "EzUI/Window.h" //基础窗口类 #include "EzUI/Window.h" //基础窗口类
#include "EzUI/Button.h" //按钮 #include "EzUI/Button.h" //按钮
@@ -19,3 +18,4 @@
#include "EzUI/LayeredWindow.h"//分层窗口类-可以异型透明窗口 #include "EzUI/LayeredWindow.h"//分层窗口类-可以异型透明窗口
#include "ezui/UIManager.h"//ui管理类(使用xml生成控件) #include "ezui/UIManager.h"//ui管理类(使用xml生成控件)
#include "EzUI/Animation.h" #include "EzUI/Animation.h"

View File

@@ -1,4 +1,4 @@
#include "pch.h" #include "pch.h"
#include "loginForm.h" #include "loginForm.h"
#include "mainForm.h" #include "mainForm.h"
@@ -10,30 +10,30 @@ void loginForm::OnNotify(Control* sender, EventArgs& args)
TextBox* editpwd = (TextBox*)FindControl("pwd"); TextBox* editpwd = (TextBox*)FindControl("pwd");
CheckBox* ckbox = (CheckBox*)FindControl("ckbox"); CheckBox* ckbox = (CheckBox*)FindControl("ckbox");
/*if (!ckbox->GetCheck()) { /*if (!ckbox->GetCheck()) {
::MessageBoxW(Hwnd(), L"<EFBFBD><EFBFBD><EFBFBD>Ķ<EFBFBD>Э<EFBFBD><EFBFBD><EFBFBD>ѡ!", L"<EFBFBD><EFBFBD>ʾ", MB_OK); ::MessageBoxW(Hwnd(), L"请阅读协议并勾选!", L"提示", MB_OK);
return; return;
}*/ }*/
UIString user = editUser->GetText(); UIString user = editUser->GetText();
UIString pwd = editpwd->GetText(); UIString pwd = editpwd->GetText();
Hide(); // <EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD>¼<EFBFBD><EFBFBD><EFBFBD><EFBFBD> Hide(); // 隐藏登录窗口
static mainForm mainForm; static mainForm mainForm;
mainForm.Show(); // <EFBFBD><EFBFBD>ʾ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> mainForm.Show(); // 显示主界面
//if (user == "ad" && pwd == "123") { //if (user == "ad" && pwd == "123") {
// ::MessageBoxW(Hwnd(), L"<EFBFBD><EFBFBD>¼<EFBFBD>ɹ<EFBFBD>!", L"<EFBFBD><EFBFBD>ʾ", MB_OK); // ::MessageBoxW(Hwnd(), L"登录成功!", L"提示", MB_OK);
// Hide(); // <EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD>¼<EFBFBD><EFBFBD><EFBFBD><EFBFBD> // Hide(); // 隐藏登录窗口
// static mainForm mainForm; // static mainForm mainForm;
// mainForm.Show(); // <EFBFBD><EFBFBD>ʾ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> // mainForm.Show(); // 显示主界面
//} //}
//else { //else {
// ::MessageBoxW(Hwnd(), L"<EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>!", L"<EFBFBD><EFBFBD>ʾ", MB_OK); // ::MessageBoxW(Hwnd(), L"用户名或密码错误!", L"提示", MB_OK);
//} //}
} }
if (sender->Name == "btnExit") { if (sender->Name == "btnExit") {
Application::Exit(); Application::Exit();
} }
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> // 打开链接
if (!sender->GetAttribute("url").empty()) { if (!sender->GetAttribute("url").empty()) {
::ShellExecuteA(0, "open", sender->GetAttribute("url").c_str(), "", "", SW_SHOW); ::ShellExecuteA(0, "open", sender->GetAttribute("url").c_str(), "", "", SW_SHOW);
} }
@@ -48,7 +48,7 @@ void loginForm::OnClose(bool& close)
loginForm::loginForm() :LayeredWindow(320, 448) loginForm::loginForm() :LayeredWindow(320, 448)
{ {
umg.LoadXml("res/loginForm.htm");//<EFBFBD><EFBFBD><EFBFBD><EFBFBD>xml<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀؼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ umg.LoadXml("res/loginForm.htm");//加载xml里面的控件与样式
umg.SetupUI(this); umg.SetupUI(this);
} }

View File

@@ -1,21 +1,21 @@
#pragma once #pragma once
#include "pch.h" #include "pch.h"
using namespace ezui; using namespace ezui;
using Form = LayeredWindow; //֧<><D6A7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>͸<EFBFBD><CDB8><EFBFBD><EFBFBD><EFBFBD><EFBFBD>(<28><><EFBFBD><EFBFBD>Ӱ) //using Form = LayeredWindow; //支持异形透明窗口(带阴影)
//using Form = BorderlessWindow; //<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ޱ߿򴰿<EFBFBD>(<28><><EFBFBD><EFBFBD>Ӱ) //using Form = BorderlessWindow; //常规无边框窗口(带阴影)
//using Form = Window; //<EFBFBD><EFBFBD>׼<EFBFBD><EFBFBD><EFBFBD><EFBFBD>(<28><>ϵͳ<CFB5><CDB3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>) //using Form = Window; //标准窗口(带系统标题栏)
// <EFBFBD><EFBFBD>½<EFBFBD><EFBFBD><EFBFBD><EFBFBD> // 登陆窗口
class loginForm :public Form class loginForm :public LayeredWindow
{ {
private: private:
//ui<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> //ui管理类
UIManager umg; UIManager umg;
protected: protected:
virtual void OnNotify(Control* sender, EventArgs& args)override;//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>¼<EFBFBD>֪ͨ virtual void OnNotify(Control* sender, EventArgs& args)override;//重载事件通知
virtual void OnClose(bool& close)override;//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڹرյ<EFBFBD>ʱ<EFBFBD><EFBFBD> virtual void OnClose(bool& close)override;//当窗口关闭的时候
public: public:
loginForm(); loginForm();
virtual ~loginForm(); virtual ~loginForm();

View File

@@ -1,28 +1,64 @@
#include "pch.h" #include "pch.h"
#include "mainForm.h" #include "mainForm.h"
void mainForm::OnNotify(Control* sender, EventArgs& args) void mainForm::OnNotify(Control* sender, EventArgs& args)
{ {
UIString btnName = sender->Name; // <EFBFBD>ؼ<EFBFBD>id UIString btnName = sender->Name; // 控件id
Event eventType = args.EventType; // <EFBFBD>¼<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Event eventType = args.EventType; // 事件类型
switch (eventType) switch (eventType)
{ {
case ezui::OnMouseDown: //<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> case ezui::OnMouseDown: //鼠标按下
{ {
if (btnName == "btnExitMain") { //<EFBFBD>˳<EFBFBD> if (btnName == "btnExitMain") { //退出
int result = ::MessageBoxW(Hwnd(), L"Ҫ<EFBFBD>˳<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>", L"<EFBFBD><EFBFBD>ʾ", MB_YESNO | MB_ICONQUESTION); int result = ::MessageBoxW(Hwnd(), L"要退出程序吗?", L"提示", MB_YESNO | MB_ICONQUESTION);
if (result == IDYES) if (result == IDYES)
exit(0); exit(0);
} }
else if (btnName == "btnMinMain") { //<EFBFBD><EFBFBD>С<EFBFBD><EFBFBD> else if (btnName == "btnMinMain") { //最小化
this->ShowMinimized(); this->ShowMinimized();
} }
else if (btnName == "btnMaxMain") { //最大化|还原
if (this->IsMaximized()) {
this->ShowNormal();
// 修改控件文字
Button* btnMax = (Button*)FindControl("btnMaxMain");
btnMax->SetText(L"🗖");
}
else {
this->ShowMaximized();
// 修改控件文字
Button* btnMax = (Button*)FindControl("btnMaxMain");
btnMax->SetText(L"🗗");
}
}
else if (btnName == "btnAdmin") { //到管理页面 获取 mainTab 设置页面为0
TabLayout* mainTab = (TabLayout*)FindControl("mainTab");
mainTab->SetPageIndex(0);
mainTab->Invalidate();
}
else if (btnName == "btnTools") { //到工具页面 获取 mainTab 设置页面为1
TabLayout* mainTab = (TabLayout*)FindControl("mainTab");
mainTab->SetPageIndex(1);
mainTab->Invalidate();
}
else if (btnName == "btnAdminConnect") { //管理页面的连接按钮按下,先获取 btnAdminConnect 按钮,设置为不可用,修改文字为“连接中...”
Button* btnAdminConnect = (Button*)FindControl("btnAdminConnect");
btnAdminConnect->SetText(L"🔄");
btnAdminConnect->Style.BackColor = Color(0, 185, 107);
}
else if (btnName == "btnAdminTemp") { //管理页面的测试按钮
TextBox* textAdmin = (TextBox*)FindControl("textAdminOutput"); //获取输出框
//textAdmin->Insert(L"\n测试成功", true);
if (textAdmin) {
textAdmin->Insert(L"\n测试成功!",true);
}
}
} }
break; break;
case ezui::OnMouseDoubleClick: //<EFBFBD><EFBFBD><EFBFBD><EFBFBD>˫<EFBFBD><EFBFBD> case ezui::OnMouseDoubleClick: //鼠标双击
{ {
//if (btnName == "titleMain") { //<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> //if (btnName == "titleMain") { //标题布局
// if (this->IsMaximized()) { // if (this->IsMaximized()) {
// this->ShowNormal(); // this->ShowNormal();
// } // }
@@ -49,9 +85,9 @@ void mainForm::OnClose(bool& close)
mainForm::mainForm() :LayeredWindow(1000, 750) mainForm::mainForm() :LayeredWindow(1000, 750)
{ {
SetResizable(true); // <EFBFBD><EFBFBD><EFBFBD>ô<EFBFBD><EFBFBD>ڴ<EFBFBD>С<EFBFBD><EFBFBD><EFBFBD><EFBFBD> SetResizable(true); // 启用窗口大小调整
SetMiniSize(Size(600, 450)); // <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>С<EFBFBD>ߴ<EFBFBD> SetMiniSize(Size(600, 450)); // 设置最小尺寸
umg.LoadXml("res/mainForm.htm");//<EFBFBD><EFBFBD><EFBFBD><EFBFBD>xml<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀؼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ umg.LoadXml("res/mainForm.htm");//加载xml里面的控件与样式
umg.SetupUI(this); umg.SetupUI(this);
} }

View File

@@ -1,18 +1,18 @@
#pragma once #pragma once
#include "pch.h" #include "pch.h"
using namespace ezui; using namespace ezui;
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> // 主窗口
class mainForm :public LayeredWindow class mainForm :public LayeredWindow
{ {
private: private:
//ui<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> //ui管理类
UIManager umg; UIManager umg;
protected: protected:
virtual void OnNotify(Control* sender, EventArgs& args)override;//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>¼<EFBFBD>֪ͨ virtual void OnNotify(Control* sender, EventArgs& args)override;//重载事件通知
virtual void OnClose(bool& close)override;//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڹرյ<EFBFBD>ʱ<EFBFBD><EFBFBD> virtual void OnClose(bool& close)override;//当窗口关闭的时候
public: public:
mainForm(); mainForm();
virtual ~mainForm(); virtual ~mainForm();

View File

@@ -1 +1 @@
#include "pch.h" #include "pch.h"

View File

@@ -1,2 +1,2 @@
#pragma once #pragma once
#include "framework.h" #include "framework.h"

View File

@@ -39,12 +39,17 @@ namespace ezui {
std::wstring m_placeholder;//placeholder懂得都懂 (在没有文字的情况下显示的文字) std::wstring m_placeholder;//placeholder懂得都懂 (在没有文字的情况下显示的文字)
std::wstring m_passwordChar; std::wstring m_passwordChar;
bool m_readOnly = false;//是否只读 bool m_readOnly = false;//是否只读
// 内边距(四边独立)
int m_padLeft = 6;
int m_padTop = 4;
int m_padRight = 6;
int m_padBottom = 4;
public: public:
//文字对其方式(针对单行输入框有效) //文字对其方式(针对单行输入框有效)
TextAlign TextAlign = TextAlign::MiddleLeft; TextAlign TextAlign = TextAlign::MiddleLeft;
private: private:
void Init(); void Init();
void InsertUnicode(const std::wstring& str);//插入unicode文字内部使用 void InsertUnicode(const std::wstring& str, bool isEnd = false);//插入unicode文字内部使用
bool DeleteRange();//删除选中内容 bool DeleteRange();//删除选中内容
bool GetSelectedRange(int* outPos, int* outCount);//获取当前被选中的区域 返回下标和个数 bool GetSelectedRange(int* outPos, int* outCount);//获取当前被选中的区域 返回下标和个数
bool Copy();//复制到剪切板 bool Copy();//复制到剪切板
@@ -69,18 +74,24 @@ namespace ezui {
virtual void OnKillFocus(const KillFocusEventArgs& arg) override; virtual void OnKillFocus(const KillFocusEventArgs& arg) override;
virtual void OnLayout(); virtual void OnLayout();
void Offset(int moveY); void Offset(int moveY);
// 支持 TextBox 自身扩展样式属性padding 系列)
virtual bool ApplyStyleProperty(const UIString& key, const UIString& value) override;
public: public:
std::function<void(const UIString&)> TextChanged = NULL; std::function<void(const UIString&)> TextChanged = NULL;
public: public:
TextBox(Object* parentObject = NULL); TextBox(Object* parentObject = NULL);
virtual ~TextBox(); virtual ~TextBox();
// 设置内边距 (兼容旧接口:水平=px, 垂直=py)
void SetPadding(int px, int py);
// 设置四边
void SetPadding4(int left, int top, int right, int bottom);
virtual void SetAttribute(const UIString& key, const UIString& value)override; virtual void SetAttribute(const UIString& key, const UIString& value)override;
//获取焦点所在光标位置 //获取焦点所在光标位置
virtual Rect GetCareRect()override; virtual Rect GetCareRect()override;
//分析字符串 //分析字符串
void Analysis(); void Analysis();
//在当前光标中插入文字 //在当前光标中插入文字
void Insert(const UIString& str); void Insert(const UIString& str, bool isEnd = false);
//获取输入框文字 //获取输入框文字
const UIString GetText(); const UIString GetText();
//获取滚动条 //获取滚动条

View File

@@ -24,6 +24,7 @@ file(GLOB src ./ResPackage/*.*)
add_executable(ResPackage WIN32 ${src} ) add_executable(ResPackage WIN32 ${src} )
target_include_directories(ResPackage PRIVATE ${include}) target_include_directories(ResPackage PRIVATE ${include})
target_link_libraries(ResPackage PRIVATE EzUI) target_link_libraries(ResPackage PRIVATE EzUI)
set_property(TARGET ResPackage PROPERTY FOLDER "demo")
#仿QQ登录界面 #仿QQ登录界面

Binary file not shown.

View File

@@ -39,12 +39,17 @@ namespace ezui {
std::wstring m_placeholder;//placeholder懂得都懂 (在没有文字的情况下显示的文字) std::wstring m_placeholder;//placeholder懂得都懂 (在没有文字的情况下显示的文字)
std::wstring m_passwordChar; std::wstring m_passwordChar;
bool m_readOnly = false;//是否只读 bool m_readOnly = false;//是否只读
// 内边距(四边独立)
int m_padLeft = 6;
int m_padTop = 4;
int m_padRight = 6;
int m_padBottom = 4;
public: public:
//文字对其方式(针对单行输入框有效) //文字对其方式(针对单行输入框有效)
TextAlign TextAlign = TextAlign::MiddleLeft; TextAlign TextAlign = TextAlign::MiddleLeft;
private: private:
void Init(); void Init();
void InsertUnicode(const std::wstring& str);//插入unicode文字内部使用 void InsertUnicode(const std::wstring& str, bool isEnd = false);//插入unicode文字内部使用
bool DeleteRange();//删除选中内容 bool DeleteRange();//删除选中内容
bool GetSelectedRange(int* outPos, int* outCount);//获取当前被选中的区域 返回下标和个数 bool GetSelectedRange(int* outPos, int* outCount);//获取当前被选中的区域 返回下标和个数
bool Copy();//复制到剪切板 bool Copy();//复制到剪切板
@@ -69,18 +74,24 @@ namespace ezui {
virtual void OnKillFocus(const KillFocusEventArgs& arg) override; virtual void OnKillFocus(const KillFocusEventArgs& arg) override;
virtual void OnLayout(); virtual void OnLayout();
void Offset(int moveY); void Offset(int moveY);
// 支持 TextBox 自身扩展样式属性padding 系列)
virtual bool ApplyStyleProperty(const UIString& key, const UIString& value) override;
public: public:
std::function<void(const UIString&)> TextChanged = NULL; std::function<void(const UIString&)> TextChanged = NULL;
public: public:
TextBox(Object* parentObject = NULL); TextBox(Object* parentObject = NULL);
virtual ~TextBox(); virtual ~TextBox();
// 设置内边距 (兼容旧接口:水平=px, 垂直=py)
void SetPadding(int px, int py);
// 设置四边
void SetPadding4(int left, int top, int right, int bottom);
virtual void SetAttribute(const UIString& key, const UIString& value)override; virtual void SetAttribute(const UIString& key, const UIString& value)override;
//获取焦点所在光标位置 //获取焦点所在光标位置
virtual Rect GetCareRect()override; virtual Rect GetCareRect()override;
//分析字符串 //分析字符串
void Analysis(); void Analysis();
//在当前光标中插入文字 //在当前光标中插入文字
void Insert(const UIString& str); void Insert(const UIString& str, bool isEnd = false);
//获取输入框文字 //获取输入框文字
const UIString GetText(); const UIString GetText();
//获取滚动条 //获取滚动条

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -44,6 +44,45 @@ namespace ezui {
__super::OnRemove(); __super::OnRemove();
m_timer->Stop(); 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::SetAutoWidth(bool flag)
{ {
//需要屏蔽 //需要屏蔽
@@ -81,35 +120,29 @@ namespace ezui {
point2 = m_textLayout->HitTestTextPosition(m_A_TextPos, m_A_isTrailingHit); point2 = m_textLayout->HitTestTextPosition(m_A_TextPos, m_A_isTrailingHit);
point1 = m_textLayout->HitTestTextPosition(m_B_TextPos, m_B_isTrailingHit); point1 = m_textLayout->HitTestTextPosition(m_B_TextPos, m_B_isTrailingHit);
} }
// 中心偏移用于 hit test 保证在行内 // 为了稳定行判定,对基线 Y 加一个行高一半偏移放到行中部
float offsetY = m_textLayout->GetFontHeight() / 2.0f; float sampleY1 = point1.Y + m_textLayout->GetFontHeight() / 2.0f;
point1.Y += offsetY; float sampleY2 = point2.Y + m_textLayout->GetFontHeight() / 2.0f;
point2.Y += offsetY;
// 获取每行矩形
auto lineRects = m_textLayout->GetLineRects(); auto lineRects = m_textLayout->GetLineRects();
for (auto& lr : lineRects) { for (auto& lr : lineRects) {
// 首行 bool firstLine = (sampleY1 >= lr.Y && sampleY1 < lr.Y + lr.Height + 0.5f);
if (point1.Y >= lr.Y && point1.Y <= lr.Y + lr.Height) { bool lastLine = (sampleY2 >= lr.Y && sampleY2 < lr.Y + lr.Height + 0.5f);
if (point2.Y <= lr.Y + lr.Height) { if (firstLine && lastLine) { // 同一行
// 同一行 float leftX = point1.X;
float width = std::max(0.0f, float(point2.X - point1.X)); float rightX = point2.X;
m_selectRects.push_back(Rect(point1.X, lr.Y, width, lr.Height)); if (rightX < leftX) std::swap(leftX, rightX);
} float width = std::max(0.0f, rightX - leftX);
else { m_selectRects.push_back(Rect(leftX, lr.Y, width, lr.Height));
// 跨行首行 从 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 (firstLine) { // 首行:到行尾
else if (point2.Y >= lr.Y && point2.Y <= lr.Y + lr.Height) { 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); float width = std::max(0.0f, point2.X - lr.X);
m_selectRects.push_back(Rect(lr.X, lr.Y, width, lr.Height)); m_selectRects.push_back(Rect(lr.X, lr.Y, width, lr.Height));
} }
// 中间整行 else if (sampleY1 < lr.Y && sampleY2 >= lr.Y + lr.Height) { // 中间整行
else if (point1.Y < lr.Y && point2.Y > lr.Y + lr.Height) {
m_selectRects.push_back(lr); m_selectRects.push_back(lr);
} }
} }
@@ -166,12 +199,13 @@ namespace ezui {
} }
return false; return false;
} }
void TextBox::InsertUnicode(const std::wstring& str) { void TextBox::InsertUnicode(const std::wstring& str, bool isEnd) {
DeleteRange();//先删除是否有选中的区域 DeleteRange();//先删除是否有选中的区域
if (m_textPos < 0)m_textPos = 0; if (m_textPos < 0) m_textPos = 0; // 处理光标位置
if (m_textPos > (int)m_text.size()) { if (m_textPos > (int)m_text.size()) {
m_textPos = m_text.size(); m_textPos = m_text.size();
} }
if (isEnd) m_textPos = m_text.size(); // 是否让光标移到末尾
m_text.insert(m_textPos, str); m_text.insert(m_textPos, str);
m_textPos += str.size(); m_textPos += str.size();
if (TextChanged) { if (TextChanged) {
@@ -336,35 +370,45 @@ namespace ezui {
*drawText = m_text; *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) {//单行编辑框 if (!m_multiLine) {//单行编辑框
m_font->Get()->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP); m_font->Get()->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
bool bAlignLeft = (int(this->TextAlign) & int(HAlign::Left)); bool bAlignLeft = (int(this->TextAlign) & int(HAlign::Left));
float width = bAlignLeft ? EZUI_FLOAT_MAX : Width(); float width = bAlignLeft ? EZUI_FLOAT_MAX : (float)innerW;
m_textLayout = new TextLayout(*drawText, *m_font, SizeF{ width,(float)Height() }, this->TextAlign); m_textLayout = new TextLayout(*drawText, *m_font, SizeF{ width,(float)innerH }, this->TextAlign);
m_fontBox = m_textLayout->GetFontBox(); m_fontBox = m_textLayout->GetFontBox();
if (m_fontBox.Width < this->Width()) { if (m_fontBox.Width < innerW) {
m_scrollX = 0; m_scrollX = 0;
} }
if (!bAlignLeft && m_fontBox.Width > this->Width()) { if (!bAlignLeft && m_fontBox.Width > innerW) {
ezui::TextAlign tmp = this->TextAlign; ezui::TextAlign tmp = this->TextAlign;
tmp = ezui::TextAlign((int)tmp & ~(int)HAlign::Center); tmp = ezui::TextAlign((int)tmp & ~(int)HAlign::Center);
tmp = ezui::TextAlign((int)tmp & ~(int)HAlign::Right); tmp = ezui::TextAlign((int)tmp & ~(int)HAlign::Right);
tmp = ezui::TextAlign((int)tmp | (int)HAlign::Left); tmp = ezui::TextAlign((int)tmp | (int)HAlign::Left);
m_textLayout->SetTextAlign(tmp); m_textLayout->SetTextAlign(tmp);
} }
if (m_fontBox.Width > this->Width() && m_scrollX + m_fontBox.Width < this->Width()) { if (m_fontBox.Width > innerW && m_scrollX + m_fontBox.Width < innerW) {
m_scrollX = this->Width() - m_fontBox.Width; m_scrollX = innerW - m_fontBox.Width;
} }
} }
else {//多行编辑框 else {//多行编辑框
m_font->Get()->SetWordWrapping(DWRITE_WORD_WRAPPING_WRAP); 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(); m_fontBox = m_textLayout->GetFontBox();
} }
if (drawText != &this->m_text) { if (drawText != &this->m_text) {
delete drawText; 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) { if (m_multiLine) {
this->GetScrollBar()->RefreshScroll(); this->GetScrollBar()->RefreshScroll();
} }
@@ -388,14 +432,26 @@ namespace ezui {
m_careRect.Width = 1 * this->GetScale(); m_careRect.Width = 1 * this->GetScale();
if (!m_multiLine) { if (!m_multiLine) {
//使光标一直在输入框内 // 使光标保持在可视内区(考虑 padding
int drawX = m_careRect.X + m_scrollX; int innerW = std::max(0, Width() - (m_padLeft + m_padRight));
if (drawX < 0) {//光标在最左侧 int caretDrawX = m_careRect.X + m_scrollX; // 布局坐标 + 滚动
m_scrollX -= drawX; if (caretDrawX < 0) { // 左越界
m_scrollX -= caretDrawX;
} }
if (drawX > Width()) {//光标在最右侧 if (caretDrawX > innerW) { // 右越界
int ofssetX = (Width() - drawX); int offsetX = innerW - caretDrawX;
m_scrollX += ofssetX; 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); __super::OnMouseWheel(arg);
if (!m_multiLine) {//单行 if (!m_multiLine) {//单行
int innerW = std::max(0, Width() - (m_padLeft + m_padRight));
int textWidth = m_fontBox.Width; 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; m_scrollX += std::abs(arg.ZDelta) * 0.5;
if (m_scrollX > 0) { if (m_scrollX > 0) {
m_scrollX = 0; m_scrollX = 0;
} }
Invalidate(); Invalidate();
} }
else if (arg.ZDelta<0 && textWidth>Width()) { else if (arg.ZDelta<0 && textWidth>innerW) {
m_scrollX -= std::abs(arg.ZDelta) * 0.5; m_scrollX -= std::abs(arg.ZDelta) * 0.5;
if (-m_scrollX + Width() > textWidth) { if (-m_scrollX + innerW > textWidth) {
m_scrollX = -(textWidth - Width()); m_scrollX = -(textWidth - innerW);
} }
Invalidate(); Invalidate();
} }
@@ -479,9 +536,10 @@ namespace ezui {
} }
Point TextBox::ConvertPoint(const Point& pt) { Point TextBox::ConvertPoint(const Point& pt) {
int _x = -m_scrollX; // 将控件坐标转换为文本布局坐标:去掉滚动与内边距偏移
int _y = -m_scrollY; int _x = -m_scrollX - m_padLeft;
return Point{ pt.X + _x,pt.Y + _y }; int _y = -m_scrollY - m_padTop;
return Point{ pt.X + _x, pt.Y + _y };
} }
void TextBox::OnMouseMove(const MouseEventArgs& arg) void TextBox::OnMouseMove(const MouseEventArgs& arg)
@@ -498,23 +556,24 @@ namespace ezui {
BuildSelectedRect(); BuildSelectedRect();
if (!m_multiLine) {//单行 if (!m_multiLine) {//单行
//当鼠标往左侧移动 // 计算去掉左 padding 的鼠标相对文本区域坐标
int innerW = std::max(0, Width() - (m_padLeft + m_padRight));
int textWidth = m_fontBox.Width; 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; m_lastX = point.X;
if (textWidth > Width() && m_scrollX < 0 && point.X < 0) { if (textWidth > innerW && m_scrollX < 0 && localX < 0) {
m_scrollX += 3; m_scrollX += 3;
Invalidate(); Invalidate();
return; return;
} }
} }
//当鼠标往右侧移动 if (m_lastX < point.X) { // 向右拖
if (m_lastX < point.X) {
m_lastX = point.X; m_lastX = point.X;
if (textWidth > Width() && point.X > Width()) { if (textWidth > innerW && localX > innerW) {
m_scrollX -= 3; m_scrollX -= 3;
if (-m_scrollX + Width() > textWidth) { if (-m_scrollX + innerW > textWidth) {
m_scrollX = -(textWidth - Width()); m_scrollX = -(textWidth - innerW);
} }
Invalidate(); Invalidate();
return; return;
@@ -593,19 +652,24 @@ namespace ezui {
Rect TextBox::GetCareRect() Rect TextBox::GetCareRect()
{ {
Rect rect(m_careRect); Rect rect(m_careRect);
rect.X += m_scrollX;//偏移 rect.X += m_scrollX + m_padLeft; // 滚动 + padding
rect.Y += m_scrollY; rect.Y += m_scrollY + m_padTop;
return rect; return rect;
} }
void TextBox::Insert(const UIString& str) void TextBox::Insert(const UIString& str, bool isEnd)
{ {
InsertUnicode(str.unicode()); InsertUnicode(str.unicode(), isEnd);
Analysis();//分析字符串 Analysis();//分析字符串
} }
void TextBox::SetAttribute(const UIString& key, const UIString& value) { void TextBox::SetAttribute(const UIString& key, const UIString& value) {
__super::SetAttribute(key, value); __super::SetAttribute(key, value);
do do
{ {
if (key == "padding" || key == "padding-x" || key == "padding-y") {
// 走统一样式解析,直接调用 ApplyStyleProperty
ApplyStyleProperty(key, value);
break;
}
if (key == "valign") { if (key == "valign") {
this->TextAlign = ezui::TextAlign((int)this->TextAlign & ~(int)VAlign::Top); 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::Mid);
@@ -687,8 +751,9 @@ namespace ezui {
if ((!bAlignCenter) || (bAlignCenter && !m_focus)) {//避免光标和placeholder重叠 if ((!bAlignCenter) || (bAlignCenter && !m_focus)) {//避免光标和placeholder重叠
Color placeholderColor = fontColor; Color placeholderColor = fontColor;
placeholderColor.SetA(fontColor.GetA() * 0.6); placeholderColor.SetA(fontColor.GetA() * 0.6);
e.Graphics.SetColor(placeholderColor); e.Graphics.SetColor(placeholderColor);
e.Graphics.DrawString(m_placeholder, RectF(0, 0, (float)Width(), (float)Height()), m_multiLine ? TextAlign::TopLeft : this->TextAlign); 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) { for (auto& it : m_selectRects) {
if (!it.IsEmptyArea()) { if (!it.IsEmptyArea()) {
RectF rect(it); RectF rect(it);
rect.X += m_scrollX;//偏移 rect.X += m_scrollX + m_padLeft; // 加上内边距
rect.Y += m_scrollY; rect.Y += m_scrollY + m_padTop;
e.Graphics.FillRectangle(rect); e.Graphics.FillRectangle(rect);
} }
} }
@@ -708,17 +773,17 @@ namespace ezui {
if (m_textLayout) { if (m_textLayout) {
e.Graphics.SetColor(fontColor); 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_careRect.IsEmptyArea() && m_focus) {
if (m_bCareShow) { 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.X += m_scrollX;//偏移
rect.Y += m_scrollY; rect.Y += m_scrollY;
if (ezui::IsFloatEqual(rect.X, this->Width())) { 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(); rect.Width = rect.Width * this->GetScale();
e.Graphics.SetColor(fontColor); e.Graphics.SetColor(fontColor);