diff --git a/CMakeLists.txt b/CMakeLists.txt index 3011867..62c98fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,4 +44,7 @@ function(add_resource_package TARGET_NAME INPUT_DIR OUTPUT_FILE) endfunction() #添加子项目 -#add_subdirectory(./demo) # 暂时注释掉,只编译库 +option(BUILD_DEMO "Build demo projects" ON) +if(BUILD_DEMO) + add_subdirectory(./demo) +endif() diff --git a/demo/Adminstor/Adminstor/DemoUi.aps b/demo/Adminstor/Adminstor/DemoUi.aps index 9df5665..af2d2b8 100644 Binary files a/demo/Adminstor/Adminstor/DemoUi.aps and b/demo/Adminstor/Adminstor/DemoUi.aps differ diff --git a/demo/Adminstor/Adminstor/DemoUi.cpp b/demo/Adminstor/Adminstor/DemoUi.cpp index 950bf8e..f62fc9f 100644 --- a/demo/Adminstor/Adminstor/DemoUi.cpp +++ b/demo/Adminstor/Adminstor/DemoUi.cpp @@ -7,7 +7,6 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance Application app(hInstance); app.EnableHighDpi();//启用高DPI - //创建登录创建 loginForm loginFrm; loginFrm.Show(); diff --git a/demo/Adminstor/Adminstor/DemoUi.rc b/demo/Adminstor/Adminstor/DemoUi.rc index 8d170ab..ff26827 100644 Binary files a/demo/Adminstor/Adminstor/DemoUi.rc and b/demo/Adminstor/Adminstor/DemoUi.rc differ diff --git a/demo/Adminstor/Adminstor/Resource.h b/demo/Adminstor/Adminstor/Resource.h index 9f75d0a..10beec9 100644 --- a/demo/Adminstor/Adminstor/Resource.h +++ b/demo/Adminstor/Adminstor/Resource.h @@ -1,2 +1,17 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. +//{{NO_DEPENDENCIES}} +// 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 diff --git a/demo/Adminstor/Adminstor/framework.h b/demo/Adminstor/Adminstor/framework.h index 5e85361..c420462 100644 --- a/demo/Adminstor/Adminstor/framework.h +++ b/demo/Adminstor/Adminstor/framework.h @@ -3,7 +3,6 @@ #include - #include "ezui/Application.h" //app类 #include "EzUI/Window.h" //基础窗口类 #include "EzUI/Button.h" //按钮 @@ -19,3 +18,4 @@ #include "EzUI/LayeredWindow.h"//分层窗口类-可以异型透明窗口 #include "ezui/UIManager.h"//ui管理类(使用xml生成控件) #include "EzUI/Animation.h" + diff --git a/demo/Adminstor/Adminstor/loginForm.cpp b/demo/Adminstor/Adminstor/loginForm.cpp index caadcc1..43ca279 100644 --- a/demo/Adminstor/Adminstor/loginForm.cpp +++ b/demo/Adminstor/Adminstor/loginForm.cpp @@ -1,4 +1,4 @@ -#include "pch.h" +#include "pch.h" #include "loginForm.h" #include "mainForm.h" @@ -10,30 +10,30 @@ void loginForm::OnNotify(Control* sender, EventArgs& args) TextBox* editpwd = (TextBox*)FindControl("pwd"); CheckBox* ckbox = (CheckBox*)FindControl("ckbox"); /*if (!ckbox->GetCheck()) { - ::MessageBoxW(Hwnd(), L"ĶЭ鲢ѡ!", L"ʾ", MB_OK); + ::MessageBoxW(Hwnd(), L"请阅读协议并勾选!", L"提示", MB_OK); return; }*/ UIString user = editUser->GetText(); UIString pwd = editpwd->GetText(); - Hide(); // ص¼ + Hide(); // 隐藏登录窗口 static mainForm mainForm; - mainForm.Show(); // ʾ + mainForm.Show(); // 显示主界面 //if (user == "ad" && pwd == "123") { - // ::MessageBoxW(Hwnd(), L"¼ɹ!", L"ʾ", MB_OK); - // Hide(); // ص¼ + // ::MessageBoxW(Hwnd(), L"登录成功!", L"提示", MB_OK); + // Hide(); // 隐藏登录窗口 // static mainForm mainForm; - // mainForm.Show(); // ʾ + // mainForm.Show(); // 显示主界面 //} //else { - // ::MessageBoxW(Hwnd(), L"û!", L"ʾ", MB_OK); + // ::MessageBoxW(Hwnd(), L"用户名或密码错误!", L"提示", MB_OK); //} } if (sender->Name == "btnExit") { Application::Exit(); } - // + // 打开链接 if (!sender->GetAttribute("url").empty()) { ::ShellExecuteA(0, "open", sender->GetAttribute("url").c_str(), "", "", SW_SHOW); } @@ -48,7 +48,7 @@ void loginForm::OnClose(bool& close) loginForm::loginForm() :LayeredWindow(320, 448) { - umg.LoadXml("res/loginForm.htm");//xmlĿؼʽ + umg.LoadXml("res/loginForm.htm");//加载xml里面的控件与样式 umg.SetupUI(this); } diff --git a/demo/Adminstor/Adminstor/loginForm.h b/demo/Adminstor/Adminstor/loginForm.h index 9454133..80fdc91 100644 --- a/demo/Adminstor/Adminstor/loginForm.h +++ b/demo/Adminstor/Adminstor/loginForm.h @@ -1,21 +1,21 @@ -#pragma once +#pragma once #include "pch.h" using namespace ezui; -using Form = LayeredWindow; //֧͸(Ӱ) -//using Form = BorderlessWindow; //ޱ߿򴰿(Ӱ) -//using Form = Window; //׼(ϵͳ) +//using Form = LayeredWindow; //支持异形透明窗口(带阴影) +//using Form = BorderlessWindow; //常规无边框窗口(带阴影) +//using Form = Window; //标准窗口(带系统标题栏) -// ½ -class loginForm :public Form +// 登陆窗口 +class loginForm :public LayeredWindow { private: - //ui + //ui管理类 UIManager umg; protected: - virtual void OnNotify(Control* sender, EventArgs& args)override;//¼֪ͨ - virtual void OnClose(bool& close)override;//ڹرյʱ + virtual void OnNotify(Control* sender, EventArgs& args)override;//重载事件通知 + virtual void OnClose(bool& close)override;//当窗口关闭的时候 public: loginForm(); virtual ~loginForm(); diff --git a/demo/Adminstor/Adminstor/mainForm.cpp b/demo/Adminstor/Adminstor/mainForm.cpp index bb43d5e..fda42b5 100644 --- a/demo/Adminstor/Adminstor/mainForm.cpp +++ b/demo/Adminstor/Adminstor/mainForm.cpp @@ -1,28 +1,64 @@ -#include "pch.h" +#include "pch.h" #include "mainForm.h" void mainForm::OnNotify(Control* sender, EventArgs& args) { - UIString btnName = sender->Name; // ؼid - Event eventType = args.EventType; // ¼ + UIString btnName = sender->Name; // 控件id + Event eventType = args.EventType; // 事件类型 switch (eventType) { - case ezui::OnMouseDown: //갴 + case ezui::OnMouseDown: //鼠标按下 { - if (btnName == "btnExitMain") { //˳ - int result = ::MessageBoxW(Hwnd(), L"Ҫ˳", L"ʾ", MB_YESNO | MB_ICONQUESTION); + if (btnName == "btnExitMain") { //退出 + int result = ::MessageBoxW(Hwnd(), L"要退出程序吗?", L"提示", MB_YESNO | MB_ICONQUESTION); if (result == IDYES) exit(0); } - else if (btnName == "btnMinMain") { //С + else if (btnName == "btnMinMain") { //最小化 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; - case ezui::OnMouseDoubleClick: //˫ + case ezui::OnMouseDoubleClick: //鼠标双击 { - //if (btnName == "titleMain") { //Ⲽ + //if (btnName == "titleMain") { //标题布局 // if (this->IsMaximized()) { // this->ShowNormal(); // } @@ -49,9 +85,9 @@ void mainForm::OnClose(bool& close) mainForm::mainForm() :LayeredWindow(1000, 750) { - SetResizable(true); // ôڴС - SetMiniSize(Size(600, 450)); // Сߴ - umg.LoadXml("res/mainForm.htm");//xmlĿؼʽ + SetResizable(true); // 启用窗口大小调整 + SetMiniSize(Size(600, 450)); // 设置最小尺寸 + umg.LoadXml("res/mainForm.htm");//加载xml里面的控件与样式 umg.SetupUI(this); } diff --git a/demo/Adminstor/Adminstor/mainForm.h b/demo/Adminstor/Adminstor/mainForm.h index f1eedab..ca6af80 100644 --- a/demo/Adminstor/Adminstor/mainForm.h +++ b/demo/Adminstor/Adminstor/mainForm.h @@ -1,18 +1,18 @@ -#pragma once +#pragma once #include "pch.h" using namespace ezui; -// +// 主窗口 class mainForm :public LayeredWindow { private: - //ui + //ui管理类 UIManager umg; protected: - virtual void OnNotify(Control* sender, EventArgs& args)override;//¼֪ͨ - virtual void OnClose(bool& close)override;//ڹرյʱ + virtual void OnNotify(Control* sender, EventArgs& args)override;//重载事件通知 + virtual void OnClose(bool& close)override;//当窗口关闭的时候 public: mainForm(); virtual ~mainForm(); diff --git a/demo/Adminstor/Adminstor/pch.cpp b/demo/Adminstor/Adminstor/pch.cpp index 1730571..331e647 100644 --- a/demo/Adminstor/Adminstor/pch.cpp +++ b/demo/Adminstor/Adminstor/pch.cpp @@ -1 +1 @@ -#include "pch.h" \ No newline at end of file +#include "pch.h" \ No newline at end of file diff --git a/demo/Adminstor/Adminstor/pch.h b/demo/Adminstor/Adminstor/pch.h index c9c7883..bfcd21d 100644 --- a/demo/Adminstor/Adminstor/pch.h +++ b/demo/Adminstor/Adminstor/pch.h @@ -1,2 +1,2 @@ -#pragma once +#pragma once #include "framework.h" diff --git a/demo/Adminstor/ThirdParty/EzUI/include/EzUI/TextBox.h b/demo/Adminstor/ThirdParty/EzUI/include/EzUI/TextBox.h index c3a8f6b..974b158 100644 --- a/demo/Adminstor/ThirdParty/EzUI/include/EzUI/TextBox.h +++ b/demo/Adminstor/ThirdParty/EzUI/include/EzUI/TextBox.h @@ -39,12 +39,17 @@ namespace ezui { std::wstring m_placeholder;//placeholder懂得都懂 (在没有文字的情况下显示的文字) std::wstring m_passwordChar; bool m_readOnly = false;//是否只读 + // 内边距(四边独立) + int m_padLeft = 6; + int m_padTop = 4; + int m_padRight = 6; + int m_padBottom = 4; public: //文字对其方式(针对单行输入框有效) TextAlign TextAlign = TextAlign::MiddleLeft; private: void Init(); - void InsertUnicode(const std::wstring& str);//插入unicode文字内部使用 + void InsertUnicode(const std::wstring& str, bool isEnd = false);//插入unicode文字内部使用 bool DeleteRange();//删除选中内容 bool GetSelectedRange(int* outPos, int* outCount);//获取当前被选中的区域 返回下标和个数 bool Copy();//复制到剪切板 @@ -69,18 +74,24 @@ namespace ezui { virtual void OnKillFocus(const KillFocusEventArgs& arg) override; virtual void OnLayout(); void Offset(int moveY); + // 支持 TextBox 自身扩展样式属性(padding 系列) + virtual bool ApplyStyleProperty(const UIString& key, const UIString& value) override; public: std::function TextChanged = NULL; public: TextBox(Object* parentObject = NULL); 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 Rect GetCareRect()override; //分析字符串 void Analysis(); //在当前光标中插入文字 - void Insert(const UIString& str); + void Insert(const UIString& str, bool isEnd = false); //获取输入框文字 const UIString GetText(); //获取滚动条 diff --git a/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Debug_Win32.lib b/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Debug_Win32.lib index a741af0..5d7ac53 100644 Binary files a/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Debug_Win32.lib and b/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Debug_Win32.lib differ diff --git a/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Debug_Win32.pdb b/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Debug_Win32.pdb index d320a33..59a5db8 100644 Binary files a/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Debug_Win32.pdb and b/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Debug_Win32.pdb differ diff --git a/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Debug_x64.lib b/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Debug_x64.lib index 1e13ca2..9746662 100644 Binary files a/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Debug_x64.lib and b/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Debug_x64.lib differ diff --git a/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Debug_x64.pdb b/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Debug_x64.pdb index a458b0b..74065a2 100644 Binary files a/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Debug_x64.pdb and b/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Debug_x64.pdb differ diff --git a/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Release_Win32.lib b/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Release_Win32.lib index ea2e7f5..65f04fb 100644 Binary files a/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Release_Win32.lib and b/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Release_Win32.lib differ diff --git a/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Release_x64.lib b/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Release_x64.lib index 08536b2..0fb7ad0 100644 Binary files a/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Release_x64.lib and b/demo/Adminstor/ThirdParty/EzUI/lib/EzUI_Release_x64.lib differ diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt index 75095fe..4ba172d 100644 --- a/demo/CMakeLists.txt +++ b/demo/CMakeLists.txt @@ -24,6 +24,7 @@ file(GLOB src ./ResPackage/*.*) add_executable(ResPackage WIN32 ${src} ) target_include_directories(ResPackage PRIVATE ${include}) target_link_libraries(ResPackage PRIVATE EzUI) +set_property(TARGET ResPackage PROPERTY FOLDER "demo") #仿QQ登录界面 diff --git a/demo/kugou/kugou.res b/demo/kugou/kugou.res index a61f6ed..ed86b7c 100644 Binary files a/demo/kugou/kugou.res and b/demo/kugou/kugou.res differ diff --git a/include/EzUI/TextBox.h b/include/EzUI/TextBox.h index c3a8f6b..974b158 100644 --- a/include/EzUI/TextBox.h +++ b/include/EzUI/TextBox.h @@ -39,12 +39,17 @@ namespace ezui { std::wstring m_placeholder;//placeholder懂得都懂 (在没有文字的情况下显示的文字) std::wstring m_passwordChar; bool m_readOnly = false;//是否只读 + // 内边距(四边独立) + int m_padLeft = 6; + int m_padTop = 4; + int m_padRight = 6; + int m_padBottom = 4; public: //文字对其方式(针对单行输入框有效) TextAlign TextAlign = TextAlign::MiddleLeft; private: void Init(); - void InsertUnicode(const std::wstring& str);//插入unicode文字内部使用 + void InsertUnicode(const std::wstring& str, bool isEnd = false);//插入unicode文字内部使用 bool DeleteRange();//删除选中内容 bool GetSelectedRange(int* outPos, int* outCount);//获取当前被选中的区域 返回下标和个数 bool Copy();//复制到剪切板 @@ -69,18 +74,24 @@ namespace ezui { virtual void OnKillFocus(const KillFocusEventArgs& arg) override; virtual void OnLayout(); void Offset(int moveY); + // 支持 TextBox 自身扩展样式属性(padding 系列) + virtual bool ApplyStyleProperty(const UIString& key, const UIString& value) override; public: std::function TextChanged = NULL; public: TextBox(Object* parentObject = NULL); 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 Rect GetCareRect()override; //分析字符串 void Analysis(); //在当前光标中插入文字 - void Insert(const UIString& str); + void Insert(const UIString& str, bool isEnd = false); //获取输入框文字 const UIString GetText(); //获取滚动条 diff --git a/lib/EzUI_Debug_Win32.lib b/lib/EzUI_Debug_Win32.lib index a741af0..5d7ac53 100644 Binary files a/lib/EzUI_Debug_Win32.lib and b/lib/EzUI_Debug_Win32.lib differ diff --git a/lib/EzUI_Debug_Win32.pdb b/lib/EzUI_Debug_Win32.pdb index d320a33..59a5db8 100644 Binary files a/lib/EzUI_Debug_Win32.pdb and b/lib/EzUI_Debug_Win32.pdb differ diff --git a/lib/EzUI_Debug_x64.lib b/lib/EzUI_Debug_x64.lib index 1e13ca2..9746662 100644 Binary files a/lib/EzUI_Debug_x64.lib and b/lib/EzUI_Debug_x64.lib differ diff --git a/lib/EzUI_Debug_x64.pdb b/lib/EzUI_Debug_x64.pdb index a458b0b..74065a2 100644 Binary files a/lib/EzUI_Debug_x64.pdb and b/lib/EzUI_Debug_x64.pdb differ diff --git a/lib/EzUI_Release_Win32.lib b/lib/EzUI_Release_Win32.lib index ea2e7f5..65f04fb 100644 Binary files a/lib/EzUI_Release_Win32.lib and b/lib/EzUI_Release_Win32.lib differ diff --git a/lib/EzUI_Release_x64.lib b/lib/EzUI_Release_x64.lib index 08536b2..0fb7ad0 100644 Binary files a/lib/EzUI_Release_x64.lib and b/lib/EzUI_Release_x64.lib differ diff --git a/sources/TextBox.cpp b/sources/TextBox.cpp index a56bff2..50fee9c 100644 --- a/sources/TextBox.cpp +++ b/sources/TextBox.cpp @@ -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);