Compare commits
1 Commits
v2.3.2
...
0c1cf2938f
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c1cf2938f |
@@ -7,9 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
[中文文档](CHANGELOG.md)
|
||||
|
||||
## [v2.3.2] - 2025 - 12 - 20
|
||||
|
||||
### ✨ Added
|
||||
|
||||
- **Table: runtime reset for headers and data:** added `Table::clearHeaders()`, `Table::clearData()`, and `Table::resetTable()`. This allows a single `Table` instance to dynamically update its headers/data at runtime, and triggers the required recalculation (cell sizing / pagination info) and redraw.
|
||||
- **TextBox: password mode:** added `PASSWORD_MODE` to `TextBoxmode`. User input is stored internally, while the render layer displays masked characters (e.g., `*`). The real text can be retrieved via `TextBox::getText()`.
|
||||
|
||||
### ⚙️ Changed
|
||||
|
||||
- **TabControl: clarified default active page semantics:**
|
||||
- Calling `TabControl::setActiveIndex()` **before the first draw** now only records the default active index; it no longer immediately triggers the tab button click callback.
|
||||
- **After the first draw completes**, if a default active index was set, the active state is applied and the active page is drawn (if the index is out of range, the last page is activated by default).
|
||||
- Calling `TabControl::setActiveIndex()` **during runtime (non-first draw)** switches the active page immediately when the index is valid; out-of-range indices are ignored.
|
||||
|
||||
### ✅ Fixed
|
||||
|
||||
- **TabControl::setActiveIndex crash when called before drawing:** fixed a null-pointer access caused by triggering the tab button click callback before initialization. The default activation is now applied after the first draw completes, preventing crashes and ensuring the active page is rendered on first draw.
|
||||
- **TabControl rendering glitches when toggling visibility (hidden -> visible):** fixed multi-page overlap/ghosting caused by non-active pages being incorrectly drawn after `setIsVisible(false) -> setIsVisible(true)`. Now, when TabControl is visible, only the active page is visible/drawable; if there is no active page, nothing is drawn.
|
||||
|
||||
## [v2.3.1] - 2025-11-30
|
||||
|
||||
## 🙏 Acknowledgements
|
||||
### 🙏 Acknowledgements
|
||||
|
||||
- Special thanks to user [@To-KongBai](https://github.com/To-KongBai) for providing stable reproduction steps and key phenomenon comparisons (container nested child control coordinate transformation issue), which helped us quickly identify and fix the control coordinate transformation problem in deeply nested containers. ([Issues#6](https://github.com/Ysm-04/StellarX/issues/6))
|
||||
- In the upcoming website (currently undergoing ICP filing), we plan to add a contributors' wall to acknowledge users. We welcome everyone to report bugs or share interfaces created with StellarX, and we will carefully read and include acknowledgements.
|
||||
|
||||
21
CHANGELOG.md
21
CHANGELOG.md
@@ -7,9 +7,28 @@ StellarX 项目所有显著的变化都将被记录在这个文件中。
|
||||
|
||||
[English document](CHANGELOG.en.md)
|
||||
|
||||
## [v2.3.2] - 2025 - 12 - 20
|
||||
|
||||
### ✨ 新增
|
||||
|
||||
- **Table 支持运行期重置表头与数据:**新增 `Table::clearHeaders()`、`Table::clearData()`、`Table::resetTable()`,允许同一 `Table` 在运行过程中动态切换表头与数据,并触发必要的单元格尺寸/分页信息重算与重绘。
|
||||
- **TextBox 新增密码模式:**`TextBoxmode` 新增 `PASSWORD_MODE`;输入内容内部保存,绘制层面使用掩码字符(如 `*`)替代显示,真实文本可通过 `TextBox::getText()` 获取。
|
||||
|
||||
### ⚙️ 变更
|
||||
|
||||
- **TabControl 默认激活页语义明确化:**
|
||||
- 首次绘制前调用 `TabControl::setActiveIndex()`:仅记录默认激活索引,不再立即触发页签按钮点击回调;
|
||||
- 首次绘制完成后:若设置了默认激活索引则应用激活状态并绘制激活页(索引越界时默认激活最后一个页);
|
||||
- 程序运行过程中调用 `TabControl::setActiveIndex()`:索引合法则立即切换激活页并绘制;索引越界则不做处理。
|
||||
|
||||
### ✅ 修复
|
||||
|
||||
- **TabControl::setActiveIndex 绘制前调用导致程序中断:**修复绘制前设置默认激活索引时触发空指针访问的问题;现在默认激活逻辑延后到首次绘制完成后再生效,避免崩溃并保证首次绘制即可绘制激活页。
|
||||
- **TabControl 由不可见设置为可见时绘制错乱:**修复 `setIsVisible(false) -> setIsVisible(true)` 后非激活页被错误绘制导致的多页重叠/残影;现在 TabControl 可见时仅激活页可见/可绘制,无激活页则不绘制任何页。
|
||||
|
||||
## [v2.3.1] - 2025 - 11 - 30
|
||||
|
||||
## 🙏 鸣谢
|
||||
### 🙏 鸣谢
|
||||
|
||||
- 感谢用户 [@To-KongBai](https://github.com/To-KongBai) 提供稳定复现步骤与关键现象对比(容器嵌套孙控件坐标转换问题),帮助我们快速确认多容器嵌套时的控件坐标转换问题并修复。([Issues#6](https://github.com/Ysm-04/StellarX/issues/6))
|
||||
- 在即将上线的官网中(ICP备案中)我们计划加入一个贡献者鸣谢墙,欢迎各位用户反馈BUG或者分享自己用星垣做的界面,我们将认真阅读并收录鸣谢
|
||||
|
||||
28
README.en.md
28
README.en.md
@@ -7,8 +7,8 @@
|
||||

|
||||
[](https://github.com/Ysm-04/StellarX)
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
@@ -25,30 +25,6 @@ This is a **teaching-grade and tooling-grade** framework that helps developers u
|
||||
|
||||
------
|
||||
|
||||
## 🆕 V2.3.0 - Major Update
|
||||
|
||||
**This version represents a significant milestone, introducing a responsive layout system that transitions from static to dynamic layout management, and comprehensively resolves the previously encountered random rendering corruption issues caused by reentrant drawing operations.**
|
||||
|
||||
- **Optimized Window Resizing Mechanism**: Refactored `WndProcThunk`, `runEventLoop`, and `pumpResizeIfNeeded` to uniformly record size changes and perform centralized repainting at the end of the event loop, eliminating jitter and sequencing confusion caused by repeated redraws.
|
||||
|
||||
- **New Dialog Size Scheduling Interface**: Introduced the combination of `Window::scheduleResizeFromModal()` and `pumpResizeIfNeeded()`, enabling modal dialogs to notify the parent window of size updates even during resizing operations. Underlying controls are relayout during unified finalization while dialogs maintain their original dimensions.
|
||||
|
||||
- **Enhanced Adaptive Layout System**: Internally added the `adaptiveLayout()` function to recalculate control positions and sizes based on anchor points, allowing dual-anchored controls (left-right or top-bottom) to adaptively stretch with window resizing.
|
||||
|
||||
- **Fixed Modal Dialog Resizing Issues**: Resolved the problem where window resizing while modal dialogs were open prevented underlying controls from updating their positions and sizes according to anchor points; simultaneously eliminated ghosting artifacts caused by repeated dialog redraws.
|
||||
|
||||
- **Further Resolved Drawing Sequence Confusion**: Replaced `InvalidateRect` with `ValidateRect` during resizing operations, ensuring the window is marked as valid only after a single unified drawing pass, preventing system-triggered `WM_PAINT` messages from causing reentrancy.
|
||||
|
||||
- **Additional Fixes**: Corrected delayed background snapshot updates in tables and dialogs under certain edge cases.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
For details, please refer to the [CHANGELOG.en](CHANGELOG.en.md).
|
||||
|
||||
------
|
||||
|
||||
## 📦 Project Structure & Design Philosophy
|
||||
|
||||
StellarX adopts classic **OOP** and **modular** design with a clear structure:
|
||||
|
||||
30
README.md
30
README.md
@@ -14,8 +14,8 @@
|
||||

|
||||
[](https://github.com/Ysm-04/StellarX)
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
@@ -32,30 +32,26 @@
|
||||
|
||||
---
|
||||
|
||||
## ==公告==
|
||||
|
||||
## 🙏 鸣谢
|
||||
|
||||
- 感谢用户 [@To-KongBai](https://github.com/To-KongBai) 提供稳定复现步骤与关键现象对比(容器嵌套孙控件坐标转换问题),帮助我们快速确认多容器嵌套时的控件坐标转换问题并修复。([Issues#6](https://github.com/Ysm-04/StellarX/issues/6))
|
||||
- 在即将上线的官网中(ICP备案中)我们计划加入一个贡献者鸣谢墙,欢迎各位用户反馈BUG或者分享自己用星垣做的界面,我们将认真阅读并收录鸣谢
|
||||
- 真诚的感谢每一位反馈BUG的用户,你们的反馈将使星垣更加稳定和健壮
|
||||
## 🆕V2.3.2——重要更新
|
||||
|
||||
---
|
||||
### 新增
|
||||
|
||||
## 🆕V2.3.1——重要更新
|
||||
|
||||
### ✨ 新增
|
||||
|
||||
新增一个登录界面Demo在主仓库**examples/**目录下
|
||||
- **Table 支持运行期重置表头与数据:**新增 `Table::clearHeaders()`、`Table::clearData()`、`Table::resetTable()`,允许同一 `Table` 在运行过程中动态切换表头与数据,并触发必要的单元格尺寸/分页信息重算与重绘。
|
||||
- **TextBox 新增密码模式:**`TextBoxmode` 新增 `PASSWORD_MODE`;输入内容内部保存,绘制层面使用掩码字符(如 `*`)替代显示,真实文本可通过 `TextBox::getText()` 获取。
|
||||
|
||||
### ⚙️ 变更
|
||||
|
||||
- **Dialog背景快照机制:**`Dialog`不在自己抓取和销毁快照,**删除**重载的抓取和恢复快照的方法,完全交由基类`Canvas`处理,`Dialog`的`draw`方法中不在处理快照
|
||||
- **窗口变化重绘时控件和窗口重绘的时机调整:**主事件循环中窗口大小发生变化时先处理控件尺寸,并回贴和释放旧快照,然后再重绘新尺寸窗口,最后绘制控件
|
||||
- **TabControl 默认激活页语义明确化:**
|
||||
- 首次绘制前调用 `TabControl::setActiveIndex()`:仅记录默认激活索引,不再立即触发页签按钮点击回调;
|
||||
- 首次绘制完成后:若设置了默认激活索引则应用激活状态并绘制激活页(索引越界时默认激活最后一个页);
|
||||
- 程序运行过程中调用 `TabControl::setActiveIndex()`:索引合法则立即切换激活页并绘制;索引越界则不做处理。
|
||||
|
||||
本次针对用户反馈和已知内容进行了一些修复……
|
||||
### ✅ 修复
|
||||
|
||||
详细变更请参阅[更新日志](CHANGELOG.md)
|
||||
- **TabControl::setActiveIndex 绘制前调用导致程序中断:**修复绘制前设置默认激活索引时触发空指针访问的问题;现在默认激活逻辑延后到首次绘制完成后再生效,避免崩溃并保证首次绘制即可绘制激活页。
|
||||
- **TabControl 由不可见设置为可见时绘制错乱:**修复 `setIsVisible(false) -> setIsVisible(true)` 后非激活页被错误绘制导致的多页重叠/残影;现在 TabControl 可见时仅激活页可见/可绘制,无激活页则不绘制任何页。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -21,17 +21,14 @@
|
||||
#include "Control.h"
|
||||
#include"label.h"
|
||||
|
||||
|
||||
#define DISABLEDCOLOUR RGB(96, 96, 96) //禁用状态颜色
|
||||
#define TEXTMARGINS_X 6
|
||||
#define TEXTMARGINS_Y 4
|
||||
constexpr int bordWith = 1; //边框宽度,用于快照恢复时的偏移计算
|
||||
constexpr int bordHeight = 1; //边框高度,用于快照恢复时的偏移计算
|
||||
|
||||
|
||||
class Button : public Control
|
||||
{
|
||||
|
||||
std::string text; // 按钮上的文字
|
||||
bool click; // 是否被点击
|
||||
bool hover; // 是否被悬停
|
||||
@@ -54,8 +51,6 @@ class Button : public Control
|
||||
StellarX::FillStyle buttonFillIma = StellarX::FillStyle::BDiagonal; //按钮填充图案
|
||||
IMAGE* buttonFileIMAGE = nullptr; //按钮填充图像
|
||||
|
||||
|
||||
|
||||
std::function<void()> onClickCallback; //回调函数
|
||||
std::function<void()> onToggleOnCallback; //TOGGLE模式下的回调函数
|
||||
std::function<void()> onToggleOffCallback; //TOGGLE模式下的回调函数
|
||||
@@ -192,6 +187,4 @@ private:
|
||||
void hideTooltip();
|
||||
// 根据当前 click 状态选择文案
|
||||
void refreshTooltipTextForState();
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ protected:
|
||||
COLORREF canvasBorderClor = RGB(0, 0, 0); //边框颜色
|
||||
COLORREF canvasBkClor = RGB(255, 255, 255); //背景颜色
|
||||
|
||||
|
||||
// 清除所有子控件
|
||||
void clearAllControls();
|
||||
public:
|
||||
@@ -71,6 +70,4 @@ public:
|
||||
private:
|
||||
//用来检查对话框是否模态,此控件不做实现
|
||||
bool model() const override { return false; };
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -68,7 +68,8 @@ protected:
|
||||
|
||||
Control() : localx(0), x(0), localy(0), y(0), localWidth(100), width(100), height(100), localHeight(100) {}
|
||||
Control(int x, int y, int width, int height)
|
||||
: localx(x), x(x), localy(y), y(y), localWidth(width), width(width), height(height), localHeight(height){}
|
||||
: localx(x), x(x), localy(y), y(y), localWidth(width), width(width), height(height), localHeight(height) {
|
||||
}
|
||||
public:
|
||||
|
||||
virtual ~Control()
|
||||
|
||||
@@ -207,7 +207,8 @@ namespace StellarX
|
||||
enum class TextBoxmode
|
||||
{
|
||||
INPUT_MODE, // 用户可输入模式
|
||||
READONLY_MODE // 只读模式
|
||||
READONLY_MODE, // 只读模式
|
||||
PASSWORD_MODE// 密码模式
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,7 +45,6 @@ class Dialog : public Canvas
|
||||
std::string message; //提示信息
|
||||
std::vector<std::string> lines; //消息内容按行分割
|
||||
|
||||
|
||||
bool needsInitialization = true; //是否需要初始化
|
||||
bool close = false; //是否关闭
|
||||
bool modal = true; //是否模态
|
||||
@@ -57,7 +56,6 @@ class Dialog : public Canvas
|
||||
COLORREF buttonFalseColor = RGB(215, 215, 215); //按钮未被点击颜色
|
||||
COLORREF buttonHoverColor = RGB(224, 224, 224); //按钮悬浮颜色
|
||||
|
||||
|
||||
Button* closeButton = nullptr; //关闭按钮
|
||||
|
||||
StellarX::MessageBoxResult result = StellarX::MessageBoxResult::Cancel; // 对话框结果
|
||||
@@ -78,7 +76,6 @@ public:
|
||||
//获取对话框消息,用以去重
|
||||
std::string GetText() const;
|
||||
|
||||
|
||||
public:
|
||||
Dialog(Window& hWnd, std::string text, std::string message = "对话框", StellarX::MessageBoxType type = StellarX::MessageBoxType::OK, bool modal = true);
|
||||
~Dialog();
|
||||
@@ -109,7 +106,6 @@ public:
|
||||
//初始化
|
||||
void setInitialization(bool init);
|
||||
|
||||
|
||||
private:
|
||||
// 初始化按钮
|
||||
void initButtons();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
* @文件: StellarX.h
|
||||
* @摘要: 星垣(StellarX) GUI框架 - 主包含头文件
|
||||
* @版本: v2.3.1
|
||||
* @版本: v2.3.2
|
||||
* @描述:
|
||||
* 一个为Windows平台打造的轻量级、模块化C++ GUI框架。
|
||||
* 基于EasyX图形库,提供简洁易用的API和丰富的控件。
|
||||
@@ -11,7 +11,7 @@
|
||||
*
|
||||
* @作者: 我在人间做废物
|
||||
* @邮箱: [3150131407@qq.com] | [ysm3150131407@gmail.com]
|
||||
* @官网地址:https://stellarx-gui.top
|
||||
* @官网:https://stellarx-gui.top/
|
||||
* @仓库: [https://github.com/Ysm-04/StellarX]
|
||||
*
|
||||
* @许可证: MIT License
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
class TabControl :public Canvas
|
||||
{
|
||||
int tabBarHeight = BUTMINWIDTH; //页签栏高度
|
||||
bool IsFirstDraw = true; //首次绘制标记
|
||||
int defaultActivation = -1; //默认激活页签索引
|
||||
StellarX::TabPlacement tabPlacement = StellarX::TabPlacement::Top; //页签排列方式
|
||||
std::vector<std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>> controls; //页签/页列表
|
||||
|
||||
@@ -67,6 +69,4 @@ public:
|
||||
int indexOf(const std::string& tabText) const;
|
||||
void setDirty(bool dirty) override;
|
||||
void requestRepaint(Control* parent)override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -43,6 +43,4 @@ public:
|
||||
void setTextBkColor(COLORREF color);
|
||||
//设置标签文本
|
||||
void setText(std::string text);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
#define TABLE_STR_PAGE_MID "页/共"
|
||||
#define TABLE_STR_PAGE_SUFFIX "页"
|
||||
|
||||
|
||||
class Table :public Control
|
||||
{
|
||||
private:
|
||||
@@ -131,6 +130,12 @@ public:
|
||||
void setTableLineStyle(StellarX::LineStyle style);
|
||||
//设置边框宽度
|
||||
void setTableBorderWidth(int width);
|
||||
//清空表头
|
||||
void clearHeaders();
|
||||
//清空表格数据
|
||||
void clearData();
|
||||
//清空表头和数据
|
||||
void resetTable();
|
||||
//窗口变化丢快照+标脏
|
||||
void onWindowResize() override;
|
||||
|
||||
@@ -161,7 +166,4 @@ public:
|
||||
//获取表格尺寸
|
||||
int getTableWidth() const;
|
||||
int getTableHeight() const;
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#pragma once
|
||||
#include "Control.h"
|
||||
|
||||
|
||||
class TextBox : public Control
|
||||
{
|
||||
std::string text; //文本
|
||||
@@ -55,5 +54,3 @@ private:
|
||||
//用来检查对话框是否模态,此控件不做实现
|
||||
bool model() const override { return false; };
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
/**
|
||||
/**
|
||||
* Window(头文件)
|
||||
*
|
||||
* 设计目标:
|
||||
@@ -106,5 +105,4 @@ public:
|
||||
void scheduleResizeFromModal(int w, int h);
|
||||
private:
|
||||
void adaptiveLayout(std::unique_ptr<Control>& c, const int finalH, const int finalW);
|
||||
|
||||
};
|
||||
|
||||
@@ -163,7 +163,6 @@ void Button::initButton(const std::string text, StellarX::ButtonMode mode, Stell
|
||||
tipLabel.textStyle = this->textStyle; // 复用按钮字体样式
|
||||
}
|
||||
|
||||
|
||||
Button::~Button()
|
||||
{
|
||||
if (buttonFileIMAGE)
|
||||
@@ -274,7 +273,6 @@ void Button::draw()
|
||||
|
||||
restoreStyle();//恢复默认字体样式和颜色
|
||||
dirty = false; //标记按钮不需要重绘
|
||||
|
||||
}
|
||||
// 处理鼠标事件,检测点击和悬停状态
|
||||
// 根据按钮模式和形状进行不同的处理
|
||||
@@ -314,7 +312,6 @@ bool Button::handleEvent(const ExMessage& msg)
|
||||
// 处理鼠标点击事件
|
||||
if (msg.message == WM_LBUTTONDOWN && hover && mode != StellarX::ButtonMode::DISABLED)
|
||||
{
|
||||
|
||||
if (mode == StellarX::ButtonMode::NORMAL)
|
||||
{
|
||||
click = true;
|
||||
@@ -451,7 +448,6 @@ void Button::setROUND_RECTANGLEwidth(int width)
|
||||
{
|
||||
rouRectangleSize.ROUND_RECTANGLEwidth = width;
|
||||
this->dirty = true; // 标记需要重绘
|
||||
|
||||
}
|
||||
|
||||
void Button::setROUND_RECTANGLEheight(int height)
|
||||
@@ -489,7 +485,6 @@ void Button::setFillIma(std::string imaNAme)
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
|
||||
void Button::setButtonBorder(COLORREF Border)
|
||||
{
|
||||
buttonBorderColor = Border;
|
||||
@@ -558,7 +553,6 @@ void Button::setButtonClick(BOOL click)
|
||||
requestRepaint(parent);
|
||||
}
|
||||
|
||||
|
||||
std::string Button::getButtonText() const
|
||||
{
|
||||
return this->text;
|
||||
@@ -619,8 +613,6 @@ int Button::getButtonHeight() const
|
||||
return this->height;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Button::isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius)
|
||||
{
|
||||
double dis = sqrt(pow(mouseX - x, 2) + pow(mouseY - y, 2));
|
||||
@@ -666,11 +658,9 @@ void Button::cutButtonText()
|
||||
else
|
||||
{
|
||||
cutText = ellipsize_cjk_pref(this->text, contentW, "…"); // 全角省略号
|
||||
|
||||
}
|
||||
isUseCutText = true;
|
||||
needCutText = false;
|
||||
|
||||
}
|
||||
|
||||
void Button::hideTooltip()
|
||||
@@ -691,6 +681,3 @@ void Button::refreshTooltipTextForState()
|
||||
else if (mode == StellarX::ButtonMode::TOGGLE)
|
||||
tipLabel.setText(click ? tipTextOn : tipTextOff);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -121,7 +121,6 @@ bool Canvas::handleEvent(const ExMessage& msg)
|
||||
|
||||
void Canvas::addControl(std::unique_ptr<Control> control)
|
||||
{
|
||||
|
||||
//坐标转化
|
||||
control->setX(control->getLocalX() + this->x);
|
||||
control->setY(control->getLocalY() + this->y);
|
||||
@@ -175,7 +174,6 @@ void Canvas::setCanvasLineStyle(StellarX::LineStyle style)
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
|
||||
void Canvas::setLinewidth(int width)
|
||||
{
|
||||
this->canvaslinewidth = width;
|
||||
@@ -384,8 +382,3 @@ void Canvas::requestRepaint(Control* parent)
|
||||
else
|
||||
onRequestRepaintAsRoot();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -44,9 +44,14 @@ bool StellarX::ControlText::operator!=(const ControlText& text)
|
||||
}
|
||||
void Control::setIsVisible(bool show)
|
||||
{
|
||||
this->show = show;
|
||||
dirty = true;
|
||||
|
||||
if (!show)
|
||||
this->updateBackground();
|
||||
this->show = show;
|
||||
else
|
||||
requestRepaint(parent);
|
||||
|
||||
}
|
||||
void Control::onWindowResize()
|
||||
{
|
||||
|
||||
@@ -7,8 +7,6 @@ Dialog::Dialog(Window& h,std::string text,std::string message, StellarX::Message
|
||||
show = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Dialog::~Dialog()
|
||||
{
|
||||
}
|
||||
@@ -51,7 +49,6 @@ void Dialog::draw()
|
||||
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
||||
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
|
||||
|
||||
|
||||
int ty = y + closeButtonHeight + titleToTextMargin; // 文本起始Y坐标
|
||||
for (auto& line : lines)
|
||||
{
|
||||
@@ -67,8 +64,6 @@ void Dialog::draw()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Dialog::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
bool consume = false;
|
||||
@@ -134,7 +129,6 @@ void Dialog::SetModal(bool modal)
|
||||
this->modal = modal;
|
||||
}
|
||||
|
||||
|
||||
void Dialog::SetResult(StellarX::MessageBoxResult result)
|
||||
{
|
||||
this->result = result;
|
||||
@@ -238,8 +232,6 @@ void Dialog::Show()
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Dialog::Close()
|
||||
{
|
||||
if (!show) return;
|
||||
@@ -249,12 +241,9 @@ void Dialog::Close()
|
||||
dirty = true;
|
||||
pendingCleanup = true; // 只标记需要清理,不立即执行
|
||||
|
||||
|
||||
// 工厂模式下非模态触发回调 返回结果
|
||||
if (resultCallback && !modal)
|
||||
resultCallback(this->result);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void Dialog::setInitialization(bool init)
|
||||
@@ -267,7 +256,6 @@ void Dialog::setInitialization(bool init)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Dialog::initButtons()
|
||||
{
|
||||
controls.clear();
|
||||
@@ -391,7 +379,6 @@ void Dialog::initButtons()
|
||||
noButton->textStyle = this->textStyle;
|
||||
cancelButton->textStyle = this->textStyle;
|
||||
|
||||
|
||||
this->addControl(std::move(yesButton));
|
||||
this->addControl(std::move(noButton));
|
||||
this->addControl(std::move(cancelButton));
|
||||
@@ -717,7 +704,6 @@ void Dialog::requestRepaint(Control* parent)
|
||||
for (auto& control : controls)
|
||||
if (control->isDirty() && control->IsVisible())
|
||||
control->draw();
|
||||
|
||||
}
|
||||
else
|
||||
onRequestRepaintAsRoot();
|
||||
|
||||
@@ -173,7 +173,6 @@ void TabControl::setX(int x)
|
||||
c.first->onWindowResize();
|
||||
c.second->onWindowResize();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TabControl::setY(int y)
|
||||
@@ -192,26 +191,6 @@ void TabControl::setY(int y)
|
||||
void TabControl::draw()
|
||||
{
|
||||
if (!dirty || !show)return;
|
||||
// // 在绘制 TabControl 之前,先恢复并更新背景快照:
|
||||
//int margin = canvaslinewidth > 1 ? canvaslinewidth : 1;
|
||||
//if (hasSnap)
|
||||
//{
|
||||
// // 恢复旧快照,清除上一次绘制
|
||||
// restBackground();
|
||||
// // 如果位置或尺寸变了,或没有有效缓存,则重新抓取
|
||||
// if (!saveBkImage || saveBkX != this->x - margin || saveBkY != this->y - margin || saveWidth != this->width + margin * 2 || saveHeight != this->height + margin * 2)
|
||||
// {
|
||||
// discardBackground();
|
||||
// saveBackground(this->x - margin, this->y - margin, this->width + margin * 2, this->height + margin * 2);
|
||||
// }
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// // 首次绘制或没有快照时直接抓取背景
|
||||
// saveBackground(this->x - margin, this->y - margin, this->width + margin * 2, this->height + margin * 2);
|
||||
//}
|
||||
// // 再次恢复最新背景,保证绘制区域干净
|
||||
// restBackground();
|
||||
// 绘制画布背景和基本形状及其子画布控件
|
||||
Canvas::draw();
|
||||
for (auto& c : controls)
|
||||
@@ -224,8 +203,17 @@ void TabControl::draw()
|
||||
c.second->setDirty(true);
|
||||
c.second->draw();
|
||||
}
|
||||
dirty = false;
|
||||
|
||||
// 首次绘制时处理默认激活页签
|
||||
if (IsFirstDraw)
|
||||
{
|
||||
if (defaultActivation >= 0 && defaultActivation < (int)controls.size())
|
||||
controls[defaultActivation].first->setButtonClick(true);
|
||||
else if (defaultActivation >= (int)controls.size())//索引越界则激活最后一个
|
||||
controls[controls.size() - 1].first->setButtonClick(true);
|
||||
IsFirstDraw = false;//避免重复处理
|
||||
}
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
bool TabControl::handleEvent(const ExMessage& msg)
|
||||
@@ -298,7 +286,6 @@ void TabControl::add(std::string tabText, std::unique_ptr<Control> control)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TabControl::setTabPlacement(StellarX::TabPlacement placement)
|
||||
@@ -307,7 +294,6 @@ void TabControl::setTabPlacement(StellarX::TabPlacement placement)
|
||||
setDirty(true);
|
||||
initTabBar();
|
||||
initTabPage();
|
||||
|
||||
}
|
||||
|
||||
void TabControl::setTabBarHeight(int height)
|
||||
@@ -321,16 +307,25 @@ void TabControl::setTabBarHeight(int height)
|
||||
void TabControl::setIsVisible(bool visible)
|
||||
{
|
||||
// 先让基类 Canvas 处理自己的回贴/丢快照逻辑
|
||||
Canvas::setIsVisible(visible); // <--- 新增
|
||||
|
||||
this->show = visible;
|
||||
Canvas::setIsVisible(visible);
|
||||
for (auto& tab : controls)
|
||||
{
|
||||
if(true == visible)
|
||||
{
|
||||
tab.first->setIsVisible(visible);
|
||||
//页也要跟着关/开,否则它们会保留旧的 saveBkImage
|
||||
tab.second->setIsVisible(visible);
|
||||
if (tab.first->isClicked())
|
||||
tab.second->setIsVisible(true);
|
||||
else
|
||||
tab.second->setIsVisible(false);
|
||||
tab.second->setDirty(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
tab.first->setIsVisible(visible);
|
||||
tab.second->setIsVisible(visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TabControl::onWindowResize()
|
||||
@@ -364,20 +359,13 @@ int TabControl::getActiveIndex() const
|
||||
|
||||
void TabControl::setActiveIndex(int idx)
|
||||
{
|
||||
if (idx < 0 || idx > controls.size() - 1) return;
|
||||
if (controls[idx].first->getButtonMode() == StellarX::ButtonMode::DISABLED)return;
|
||||
if (controls[idx].first->isClicked())
|
||||
{
|
||||
if (controls[idx].second->IsVisible())
|
||||
return;
|
||||
else
|
||||
controls[idx].second->setIsVisible(true);
|
||||
}
|
||||
if (IsFirstDraw)
|
||||
defaultActivation = idx;
|
||||
else
|
||||
{
|
||||
if (idx >= 0 && idx < controls.size())
|
||||
controls[idx].first->setButtonClick(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int TabControl::count() const
|
||||
|
||||
@@ -71,5 +71,4 @@ void Label::setText(std::string text)
|
||||
{
|
||||
this->text = text;
|
||||
this->dirty = true;
|
||||
|
||||
}
|
||||
@@ -26,12 +26,10 @@ void Table::drawTable()
|
||||
dY = uY;
|
||||
uY = dY + lineHeights.at(0) + TABLE_ROW_EXTRA;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Table::drawHeader()
|
||||
{
|
||||
|
||||
const int border = tableBorderWidth > 0 ? tableBorderWidth : 0;
|
||||
// 内容区原点 = x+border, y+border
|
||||
dX = x + border;
|
||||
@@ -132,7 +130,6 @@ void Table::initButton()
|
||||
int nextW = textwidth(LPCTSTR(TABLE_STR_NEXT)) + padH * 2;
|
||||
int btnH = lblH + padV * 2;
|
||||
|
||||
|
||||
// 基于“页码标签”的矩形来摆放:
|
||||
// prev 在页码左侧 gap 处;next 在右侧 gap 处;Y 对齐 pY
|
||||
int prevX = pX - gap - prevW;
|
||||
@@ -216,7 +213,6 @@ void Table::initPageNum()
|
||||
|
||||
void Table::drawPageNum()
|
||||
{
|
||||
|
||||
pageNumtext = "第";
|
||||
pageNumtext += std::to_string(currentPage);
|
||||
pageNumtext += "页/共";
|
||||
@@ -229,7 +225,6 @@ void Table::drawPageNum()
|
||||
if (StellarX::FillMode::Null == tableFillMode)
|
||||
pageNum->setTextdisap(true);
|
||||
pageNum->draw();
|
||||
|
||||
}
|
||||
|
||||
void Table::drawButton()
|
||||
@@ -247,7 +242,6 @@ void Table::drawButton()
|
||||
this->nextButton->setDirty(true);
|
||||
prevButton->draw();
|
||||
nextButton->draw();
|
||||
|
||||
}
|
||||
|
||||
void Table::setX(int x)
|
||||
@@ -283,7 +277,8 @@ void Table::setWidth(int width)
|
||||
if (remainder > 0) {
|
||||
change += 1;
|
||||
remainder -= 1;
|
||||
} else if (remainder < 0) {
|
||||
}
|
||||
else if (remainder < 0) {
|
||||
change -= 1;
|
||||
remainder += 1;
|
||||
}
|
||||
@@ -399,10 +394,13 @@ void Table::draw()
|
||||
restBackground();
|
||||
// 绘制表头
|
||||
|
||||
dX = x;
|
||||
dY = y;
|
||||
//dX = x;
|
||||
//dY = y;
|
||||
if(isNeedDrawHeaders)
|
||||
{
|
||||
drawHeader();
|
||||
this->isNeedDrawHeaders = false;
|
||||
}
|
||||
// 绘制当前页
|
||||
drawTable();
|
||||
// 绘制页码标签
|
||||
@@ -412,7 +410,6 @@ void Table::draw()
|
||||
if (this->isShowPageButton)
|
||||
drawButton();
|
||||
|
||||
|
||||
// 恢复绘图状态
|
||||
restoreStyle();
|
||||
dirty = false; // 标记不需要重绘
|
||||
@@ -538,6 +535,31 @@ void Table::setTableBorderWidth(int width)
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void Table::clearHeaders()
|
||||
{
|
||||
this->headers.clear();
|
||||
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
||||
isNeedDrawHeaders = true; // 标记需要重新绘制表头
|
||||
isNeedButtonAndPageNum = true;// 标记需要重新计算翻页按钮和页码信息
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void Table::clearData()
|
||||
{
|
||||
this->data.clear();
|
||||
this->currentPage = 1;
|
||||
this->totalPages = 1;
|
||||
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
||||
isNeedButtonAndPageNum = true;// 标记需要重新计算翻页按钮和页码信息
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void Table::resetTable()
|
||||
{
|
||||
clearHeaders();
|
||||
clearData();
|
||||
}
|
||||
|
||||
void Table::onWindowResize()
|
||||
{
|
||||
Control::onWindowResize(); // 先处理自己
|
||||
@@ -616,5 +638,3 @@ int Table::getTableHeight() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// TextBox.cpp
|
||||
#include "TextBox.h"
|
||||
|
||||
|
||||
TextBox::TextBox(int x, int y, int width, int height, std::string text, StellarX::TextBoxmode mode, StellarX::ControlShape shape)
|
||||
:Control(x, y, width, height), text(text), mode(mode), shape(shape)
|
||||
{
|
||||
@@ -25,8 +24,22 @@ void TextBox::draw()
|
||||
|
||||
settextcolor(textStyle.color);
|
||||
setbkmode(TRANSPARENT);
|
||||
int text_width = textwidth(LPCTSTR(text.c_str()));
|
||||
int text_height = textheight(LPCTSTR(text.c_str()));
|
||||
|
||||
int text_width = 0;
|
||||
int text_height = 0;
|
||||
std::string pwdText;
|
||||
if (StellarX::TextBoxmode::PASSWORD_MODE == mode)
|
||||
{
|
||||
for (size_t i = 0; i < text.size(); ++i)
|
||||
pwdText += '*';
|
||||
text_width = textwidth(LPCTSTR(pwdText.c_str()));
|
||||
text_height = textheight(LPCTSTR(pwdText.c_str()));
|
||||
}
|
||||
else
|
||||
{
|
||||
text_width = textwidth(LPCTSTR(text.c_str()));
|
||||
text_height = textheight(LPCTSTR(text.c_str()));
|
||||
}
|
||||
|
||||
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
|
||||
saveBackground(this->x, this->y, this->width, this->height);
|
||||
@@ -37,25 +50,28 @@ void TextBox::draw()
|
||||
{
|
||||
case StellarX::ControlShape::RECTANGLE:
|
||||
fillrectangle(x, y, x + width, y + height);//有边框填充矩形
|
||||
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
StellarX::TextBoxmode::PASSWORD_MODE == mode ? outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(pwdText.c_str()))
|
||||
: outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
break;
|
||||
case StellarX::ControlShape::B_RECTANGLE:
|
||||
solidrectangle(x, y, x + width, y + height);//无边框填充矩形
|
||||
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
StellarX::TextBoxmode::PASSWORD_MODE == mode ? outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(pwdText.c_str()))
|
||||
: outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
break;
|
||||
case StellarX::ControlShape::ROUND_RECTANGLE:
|
||||
fillroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);//有边框填充圆角矩形
|
||||
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
StellarX::TextBoxmode::PASSWORD_MODE == mode ? outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(pwdText.c_str()))
|
||||
:outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
break;
|
||||
case StellarX::ControlShape::B_ROUND_RECTANGLE:
|
||||
solidroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);//无边框填充圆角矩形
|
||||
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
StellarX::TextBoxmode::PASSWORD_MODE == mode ? outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(pwdText.c_str()))
|
||||
:outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
break;
|
||||
}
|
||||
restoreStyle();
|
||||
dirty = false; //标记不需要重绘
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool TextBox::handleEvent(const ExMessage& msg)
|
||||
@@ -80,7 +96,7 @@ bool TextBox::handleEvent(const ExMessage& msg)
|
||||
if (StellarX::TextBoxmode::INPUT_MODE == mode)
|
||||
{
|
||||
char* temp = new char[maxCharLen + 1];
|
||||
dirty = InputBox(temp, (int)maxCharLen, "输入框", NULL, text.c_str(), NULL, NULL, false);
|
||||
dirty = InputBox(temp, (int)maxCharLen+1, "输入框", NULL, text.c_str(), NULL, NULL, false);
|
||||
if (dirty)text = temp;
|
||||
delete[] temp;
|
||||
temp = nullptr;
|
||||
@@ -92,6 +108,15 @@ bool TextBox::handleEvent(const ExMessage& msg)
|
||||
InputBox(NULL, (int)maxCharLen, "输出框(输入无效!)", NULL, text.c_str(), NULL, NULL, false);
|
||||
consume = true;
|
||||
}
|
||||
else if (StellarX::TextBoxmode::PASSWORD_MODE == mode)
|
||||
{
|
||||
char* temp = new char[maxCharLen + 1];
|
||||
dirty = InputBox(temp, (int)maxCharLen+1, "输入框\n不可见输入,覆盖即可", NULL, NULL, NULL, NULL, false);
|
||||
if (dirty)text = temp;
|
||||
delete[] temp;
|
||||
temp = nullptr;
|
||||
consume = true;
|
||||
}
|
||||
flushmessage(EX_MOUSE | EX_KEY);
|
||||
}
|
||||
if (dirty)
|
||||
@@ -161,5 +186,3 @@ std::string TextBox::getText() const
|
||||
{
|
||||
return this->text;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -441,7 +441,6 @@ int Window::runEventLoop()
|
||||
}
|
||||
EndBatchDraw();
|
||||
needredraw = false;
|
||||
|
||||
}
|
||||
// —— 统一收口(needResizeDirty 为真时执行一次性重绘)——
|
||||
if (needResizeDirty)
|
||||
@@ -705,7 +704,6 @@ void Window::pumpResizeIfNeeded()
|
||||
{
|
||||
setbkcolor(wBkcolor);
|
||||
cleardevice();
|
||||
|
||||
}
|
||||
width = rc.right - rc.left; height = rc.bottom - rc.top;
|
||||
|
||||
@@ -818,4 +816,3 @@ void Window::adaptiveLayout(std::unique_ptr<Control>& c, const int finalH, const
|
||||
}
|
||||
c->onWindowResize();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user