6 Commits

17 changed files with 955 additions and 2767 deletions
+45
View File
@@ -7,6 +7,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[中文文档](CHANGELOG.md) [中文文档](CHANGELOG.md)
## [v3.0.1] - 2026 - 03 - 17
==Notice==
This update changes the **semantics of `TextBox::setText`**.
If your old code manually called `draw()` after calling `setText()`, you should now remove that call. Otherwise, under the new version, old code may cause `TextBox` to flicker in some cases.
### 🙏 Acknowledgements
Special thanks to [Pengfei Zhu](https://github.com/zhupengfeivip) for helping improve the StellarX documentation, especially the **Include Directories and Library Directories Configuration** section, and for reporting the issue where passing `NULL` in window mode would cause a console window to pop up ([Issues#9](https://github.com/Ysm-04/StellarX/issues/9)).
### ⚙️ Changes
- **Changed semantics of `TextBox::setText`:**
The old dirty-mark-only behavior has been replaced with a more robust workflow.
- If the text has not changed, the function returns immediately without doing anything.
- If the text has changed, it will decide whether to redraw immediately or request an upstream redraw depending on whether the graphics context/window has already been created. If the graphics context/window has not yet been created, it will only mark itself as dirty.
- **TextBox - text overflow truncation:**
If the user types text or calls `setText()` to set text, and the text length does not exceed `maxCharLen` but its pixel width exceeds the `TextBox` width, the displayed text will be truncated by character and appended with `...` for rendering.
The full original text is still stored internally and is not affected when retrieved through getter methods.
### ✅ Fixes
- **`TabControl::getActiveIndex() const` could return the last tab index when the tab list was not empty but no tab was active:**
Fixed the issue where calling `getActiveIndex()` could sometimes return an incorrect index.
Now:
- if the tab list is empty, it returns `-1`
- if no tab is active, it returns `-1`
- if a tab is active, it returns the index of the active tab
- **Continuous full redraw while a dialog is open:**
Added a new variable `dialogOpen` to `Window`.
When a dialog is shown, `dialogOpen` is set to `true`. Only when `dialogOpen` is `true` will `needRedraw` be checked. After redrawing, `dialogOpen` is immediately reset to `false`.
- **Synthetic `WM_MOUSEMOVE` being dispatched when a dialog opens, instead of only when it closes:**
Added a condition when dispatching synthetic `WM_MOUSEMOVE`.
Now it is only synthesized and dispatched when `dialogClose` is `true`, meaning only after a dialog is closed.
- **Normal control event dispatch order in `Window::runEventLoop`:**
Changed the traversal of `controls (vector)` from forward order
`for (auto& c : controls)`
to reverse order
`for (auto it = controls.rbegin(); it != controls.rend(); ++it)`
## [v3.0.0] - 2026-01-09 ## [v3.0.0] - 2026-01-09
### ✨ New Features ### ✨ New Features
+24
View File
@@ -7,6 +7,30 @@ StellarX 项目所有显著的变化都将被记录在这个文件中。
[English document](CHANGELOG.en.md) [English document](CHANGELOG.en.md)
## [v3.0.1] - 2026 - 03 - 17
==注意==
此次更新变更了**TextBox::setText语义**之前如果再调用`setText`之后手动调用了`draw`方法现在应当删除,否则旧代码在新版本可能会造成`TextBox`闪烁(概率触发)【配置包含目录与库目录】
### 🙏 鸣谢
感谢用户[Pengfei Zhu](https://github.com/zhupengfeivip)帮StellarX完善文档【配置包含目录与库目录】部分,以及反馈在窗口模式传递参数NULL时会弹出命令行窗口的问题[Issues#9](https://github.com/Ysm-04/StellarX/issues/9)
### ⚙️ 变更
- **TextBox::setText语义变化:**由原来的仅标脏改为更健全的工作原理
- 如果文本未发生变化则立即return不做任何操作
- 如果文本变化则根据是否已经创建图形上下文/窗口决定是否立即重绘/向上请求,如果图形上下文/窗口还为创建则仅标记为脏
- **TextBox-文本溢出截断:**如果用户输入或者调用`setText`设置文本,文本不超过`maxCharLen`但是像素长度超过`TextBox`则按字符截断并添加...绘制,内部仍然保存完整的文本不影响get方法获取
### ✅ 修复
- **TabControl::getActiveIndex() const当页签对列表不为空时,如果所有页签都未激活则会返回最后一个页签的索引:**修复再调用`getActiveIndex()`概率获取错误的索引,如果页签对列表为空或者没有页签激活则返回-1,如果有页签激活则返回对应页签索引
- **对话框打开时持续持续触发全量重绘:**Window新增变量`dialogOpen`,在对话框弹出时设置`dialogOpen``true`,只有dialogOpen为真时才判断`needredraw`,在重绘后立即设置`dialogOpen``flase`
- **对话框关闭合成WM_MOUSEMOVE下发更新 可见控件hover 状态,在对话框打开时也会触发:**合成`WM_MOUSEMOVE`下发时判断是否有对话框关闭,只有`dialogClose`为真才合成并下发`WM_MOUSEMOVE`
- **Window::runEventLoop普通控件事件分发顺序:**将`controls(vector)`的正序遍历`for (auto& c : controls)`改为逆序遍历`for (auto it = controls.rbegin(); it != controls.rend(); ++it)`
## [v3.0.0] - 2026 - 01 - 09 ## [v3.0.0] - 2026 - 01 - 09
### ✨ 新增 ### ✨ 新增
+29 -1
View File
@@ -7,13 +7,41 @@ project(StellarX VERSION 2.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_CXX_STANDARD_REQUIRED True)
# 为了支持 out-of-source builds,创建构建目录
set(CMAKE_BINARY_DIR ${CMAKE_SOURCE_DIR}/build)
# 设置生成的二进制文件输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# 包含头文件目录(目前头文件都在根目录) # 包含头文件目录(目前头文件都在根目录)
include_directories(${CMAKE_SOURCE_DIR}) include_directories(${CMAKE_SOURCE_DIR})
# 源文件收集 # 通过选项设置是否启用调试信息
option(USE_DEBUG "Build with debug information" OFF)
if(USE_DEBUG)
set(CMAKE_BUILD_TYPE Debug)
else()
set(CMAKE_BUILD_TYPE Release)
endif()
# 查找源文件
file(GLOB_RECURSE SOURCES file(GLOB_RECURSE SOURCES
"${CMAKE_SOURCE_DIR}/*.cpp" "${CMAKE_SOURCE_DIR}/*.cpp"
) )
# 生成可执行文件 # 生成可执行文件
add_executable(StellarX ${SOURCES}) add_executable(StellarX ${SOURCES})
# 可以选择性地查找外部库并链接(例如 Boost,SDL2等)
# FindPackage(Boost REQUIRED)
# target_link_libraries(StellarX Boost::Boost)
# 为外部依赖配置路径
# set(Boost_DIR "path/to/boost")
# find_package(Boost REQUIRED)
# 如果有额外的库需要链接,继续在此处添加
# target_link_libraries(StellarX Boost::Boost)
+18 -20
View File
@@ -2,6 +2,11 @@
[中文README](README.md) [中文README](README.md)
official website:https://stellarx-gui.top
blog: https://blog.stellarx-gui.top
> For framework information and quick start instructions, please visit the official website. For detailed usage tutorials, please refer to the StellarX Xingyuan page on my personal blog.
------ ------
![GitHub all releases](https://img.shields.io/github/downloads/Ysm-04/StellarX/total) ![GitHub all releases](https://img.shields.io/github/downloads/Ysm-04/StellarX/total)
@@ -25,34 +30,27 @@ This is a **teaching-grade and tooling-grade** framework that helps developers u
------ ------
### 🆕V3.0.0 - Major Update ### 🆕V3.0.1 - Major Update
[CHANGELOG.en.md](CHANGELOG.en.md) [CHANGELOG.en.md](CHANGELOG.en.md)
### ✨ New Features ==Notice==
- **SxLog**: A lightweight logging system with support for log levels, tag filtering, bilingual (Chinese/English) output, and console/file logging with optional file rolling. This update changes the **semantics of `TextBox::setText`**.
- **TabControl**: Improved tab switching logic to ensure the current tab is closed before the target tab is opened. If your previous code manually called `draw()` after calling `setText()`, that call should now be removed. Otherwise, under the new version, old code may cause `TextBox` flickering in some cases.
- **Improved Setters**: Setters now only update state and mark as dirty, with drawing handled by the unified redraw flow.
### 🙏 Acknowledgements
Special thanks to [Pengfei Zhu](https://github.com/zhupengfeivip) for helping improve the StellarX documentation, especially the **Include Directories and Library Directories Configuration** section, and for reporting the issue where passing `NULL` in window mode would cause a console window to appear ([Issues#9](https://github.com/Ysm-04/StellarX/issues/9)).
### ⚙️ Changes ### ⚙️ Changes
- **TabControl Default Active Tab**: Default active tab logic clarified. First, set the active index without immediate drawing; after the first draw, the tab is activated. - **Changed semantics of `TextBox::setText`:** from the previous dirty-mark-only behavior to a more robust workflow
- If the text has not changed, it returns immediately without doing anything.
- If the text has changed, it decides whether to redraw immediately or request an upstream redraw depending on whether the graphics context/window has already been created. If the graphics context/window has not yet been created, it only marks itself as dirty.
- **TextBox - text overflow truncation:** when the user enters text or calls `setText` to set text, if the text length does not exceed `maxCharLen` but its pixel width exceeds the `TextBox` width, the displayed text will be truncated by character and appended with `...` when rendered. The full original text is still stored internally and is not affected when retrieved through getter methods.
### ✅ Fixes **For other fixes and changes, please refer to the [Changelog](CHANGELOG.md).**
- **TabControl::setActiveIndex Crash**: Fixed crash when setting the default active tab before the first draw.
- **TabControl Rendering Glitch**: Fixed issue where non-active tabs were incorrectly drawn when switching visibility.
### ⚠️ Breaking Changes
- **Button Size APIs Removed**: `getButtonWidth()` and `getButtonHeight()` removed; use `getWidth()` and `getHeight()` instead.
- **No Immediate Drawing for Setters**: Setters like `setText()` no longer trigger immediate drawing.
### 📌 Upgrade Guide
- **Button**: Replace `getButtonWidth()` / `getButtonHeight()` with `getWidth()` / `getHeight()`.
- **Setters**: Ensure a redraw mechanism after calling setters like `setText()`.
## 📦 Project Structure & Design Philosophy ## 📦 Project Structure & Design Philosophy
+15 -20
View File
@@ -3,6 +3,9 @@
[English document](README.en.md) [English document](README.en.md)
官网地址:https://stellarx-gui.top 官网地址:https://stellarx-gui.top
博客:https://blog.stellarx-gui.top
> 框架信息以及快速开始可前往官网查看,详细的使用教程可前往个人博客的StellarX星垣页面查看
> 本仓库为 **StellarX** 主仓:开发与 Issue/PR 均在 GitHub 进行。 > 本仓库为 **StellarX** 主仓:开发与 Issue/PR 均在 GitHub 进行。
> GitCode 仅为只读镜像:如需反馈请到 GitHub:https://github.com/Ysm-04/StellarX > GitCode 仅为只读镜像:如需反馈请到 GitHub:https://github.com/Ysm-04/StellarX
@@ -34,34 +37,26 @@
### 🆕V3.0.0 - 重要更新 ### 🆕V3.0.1 - 重要更新
完整版建议查看[更新日志](CHANGELOG.md) 完整版建议查看[更新日志](CHANGELOG.md)
### ✨ 新增功能 ==注意==
- **SxLog**: 轻量级日志系统,支持日志级别、标签过滤、中英文输出及控制台/文件日志,支持文件滚动。 此次更新变更了**TextBox::setText语义**之前如果再调用`setText`之后手动调用了`draw`方法现在应当删除,否则旧代码在新版本可能会造成`TextBox`闪烁(概率触发)【配置包含目录与库目录】
- **TabControl**: 改进页签切换逻辑,确保先关闭当前页再打开目标页。
- **控件 Setter 改进**: Setter 仅更新状态并标记为脏,绘制由统一重绘流程处理。 ### 🙏 鸣谢
感谢用户[Pengfei Zhu](https://github.com/zhupengfeivip)帮StellarX完善文档【配置包含目录与库目录】部分,以及反馈在窗口模式传递参数NULL时会弹出命令行窗口的问题[Issues#9](https://github.com/Ysm-04/StellarX/issues/9)
### ⚙️ 变更 ### ⚙️ 变更
- **TabControl 默认激活页**: 默认激活页逻辑明确,首次设置激活索引后才绘制。 - **TextBox::setText语义变化:**由原来的仅标脏改为更健全的工作原理
- 如果文本未发生变化则立即return不做任何操作
- 如果文本变化则根据是否已经创建图形上下文/窗口决定是否立即重绘/向上请求,如果图形上下文/窗口还为创建则仅标记为脏
- **TextBox-文本溢出截断:**如果用户输入或者调用`setText`设置文本,文本不超过`maxCharLen`但是像素长度超过`TextBox`则按字符截断并添加...绘制,内部仍然保存完整的文本不影响get方法获取
### ✅ 修复 **其余修复及变更请前往[更新日志](CHANGELOG.md)**
- **TabControl::setActiveIndex 崩溃**: 修复首次绘制前设置默认激活页时崩溃的问题。
- **TabControl 渲染错乱**: 修复 `setIsVisible` 切换时,非激活页错误绘制导致的重叠问题。
### ⚠️ 破坏性更改
- **Button 尺寸 API 移除**: 移除了 `getButtonWidth()` / `getButtonHeight()`,请使用 `getWidth()` / `getHeight()`
- **Setter 不再即时绘制**: `setText()` 等 Setter 不再触发即时绘制。
### 📌 升级指南
- **Button**: 将 `getButtonWidth()` / `getButtonHeight()` 替换为 `getWidth()` / `getHeight()`
- **Setter**: 在调用 Setter 后确保触发重绘机制。
--- ---
+16 -19
View File
@@ -22,9 +22,9 @@
#include"label.h" #include"label.h"
#define DISABLEDCOLOUR RGB(96, 96, 96) //禁用状态颜色 #define DISABLEDCOLOUR RGB(96, 96, 96) //禁用状态颜色
#define TEXTMARGINS_X 6 #define TEXTMARGINS_X 6
#define TEXTMARGINS_Y 4 #define TEXTMARGINS_Y 4
constexpr int bordWith = 1; //边框宽度,用于快照恢复时的偏移计算 constexpr int bordWith = 1; //边框宽度,用于快照恢复时的偏移计算
constexpr int bordHeight = 1; //边框高度,用于快照恢复时的偏移计算 constexpr int bordHeight = 1; //边框高度,用于快照恢复时的偏移计算
class Button : public Control class Button : public Control
@@ -39,16 +39,16 @@ class Button : public Control
int padX = TEXTMARGINS_X; // 文本最小左右内边距 int padX = TEXTMARGINS_X; // 文本最小左右内边距
int padY = TEXTMARGINS_Y; // 文本最小上下内边距 int padY = TEXTMARGINS_Y; // 文本最小上下内边距
COLORREF buttonTrueColor; // 按钮被点击后的颜色 COLORREF buttonTrueColor; // 按钮被点击后的颜色
COLORREF buttonFalseColor; // 按钮未被点击的颜色 COLORREF buttonFalseColor; // 按钮未被点击的颜色
COLORREF buttonHoverColor; // 按钮被鼠标悬停的颜色 COLORREF buttonHoverColor; // 按钮被鼠标悬停的颜色
COLORREF buttonBorderColor = RGB(0, 0, 0);// 按钮边框颜色 COLORREF buttonBorderColor = RGB(0, 0, 0);// 按钮边框颜色
StellarX::ButtonMode mode; // 按钮模式 StellarX::ButtonMode mode; // 按钮模式
StellarX::ControlShape shape; // 按钮形状 StellarX::ControlShape shape; // 按钮形状
StellarX::FillMode buttonFillMode = StellarX::FillMode::Solid; //按钮填充模式 StellarX::FillMode buttonFillMode = StellarX::FillMode::Solid; //按钮填充模式
StellarX::FillStyle buttonFillIma = StellarX::FillStyle::BDiagonal; //按钮填充图案 StellarX::FillStyle buttonFillIma = StellarX::FillStyle::BDiagonal; //按钮填充图案
IMAGE* buttonFileIMAGE = nullptr; //按钮填充图像 IMAGE* buttonFileIMAGE = nullptr; //按钮填充图像
std::function<void()> onClickCallback; //回调函数 std::function<void()> onClickCallback; //回调函数
@@ -63,20 +63,20 @@ class Button : public Control
// === Tooltip === // === Tooltip ===
bool tipEnabled = false; // 是否启用 bool tipEnabled = false; // 是否启用
bool tipVisible = false; // 当前是否显示 bool tipVisible = false; // 当前是否显示
bool tipFollowCursor = false; // 是否跟随鼠标 bool tipFollowCursor = false; // 是否跟随鼠标
bool tipUserOverride = false; // 是否用户自定义了tip文本 bool tipUserOverride = false; // 是否用户自定义了tip文本
int tipDelayMs = 1000; // 延时(毫秒) int tipDelayMs = 1000; // 延时(毫秒)
int tipOffsetX = 12; // 相对鼠标偏移 int tipOffsetX = 12; // 相对鼠标偏移
int tipOffsetY = 18; int tipOffsetY = 18;
ULONGLONG tipHoverTick = 0; // 开始悬停的时间戳 ULONGLONG tipHoverTick = 0; // 开始悬停的时间戳
int lastMouseX = 0; // 最新鼠标位置(用于定位) int lastMouseX = 0; // 最新鼠标位置(用于定位)
int lastMouseY = 0; int lastMouseY = 0;
std::string tipTextClick; //NORMAL 模式下用 std::string tipTextClick; // NORMAL 模式下用
std::string tipTextOn; // click==true 时用 std::string tipTextOn; // click==true 时用
std::string tipTextOff; // click==false 时用 std::string tipTextOff; // click==false 时用
Label tipLabel; // 直接复用Label作为提示 Label tipLabel; // 直接复用Label作为提示
public: public:
StellarX::ControlText textStyle; // 按钮文字样式 StellarX::ControlText textStyle; // 按钮文字样式
@@ -154,10 +154,6 @@ public:
COLORREF getButtonTextColor() const; COLORREF getButtonTextColor() const;
//获取按钮文字样式 //获取按钮文字样式
StellarX::ControlText getButtonTextStyle() const; StellarX::ControlText getButtonTextStyle() const;
//获取按钮宽度
int getButtonWidth() const;
//获取按钮高度
int getButtonHeight() const;
public: public:
// === Tooltip API=== // === Tooltip API===
//设置是否启用提示框 //设置是否启用提示框
@@ -182,6 +178,7 @@ private:
bool isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, int height); bool isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, int height);
//获取对话框类型 //获取对话框类型
bool model() const override { return false; } bool model() const override { return false; }
//文本截断
void cutButtonText(); void cutButtonText();
// 统一隐藏&恢复背景 // 统一隐藏&恢复背景
void hideTooltip(); void hideTooltip();
+4 -3
View File
@@ -195,7 +195,7 @@ namespace StellarX
/** /**
* @枚举类名称: TextBoxmode * @枚举类名称: TextBoxmode
* @功能描述: 定义了文本框的种模式 * @功能描述: 定义了文本框的种模式
* *
* @详细说明: * @详细说明:
* 需要限制文本框是否接受用户输入时使用 * 需要限制文本框是否接受用户输入时使用
@@ -203,12 +203,13 @@ namespace StellarX
* @取值说明: * @取值说明:
* INPUT_MODE, // 用户可输入模式 * INPUT_MODE, // 用户可输入模式
* READONLY_MODE // 只读模式 * READONLY_MODE // 只读模式
* PASSWORD_MODE // 密码模式
*/ */
enum class TextBoxmode enum class TextBoxmode
{ {
INPUT_MODE, // 用户可输入模式 INPUT_MODE, // 用户可输入模式
READONLY_MODE, // 只读模式 READONLY_MODE, // 只读模式
PASSWORD_MODE// 密码模式 PASSWORD_MODE // 密码模式
}; };
/** /**
+3 -2
View File
@@ -1,7 +1,7 @@
/******************************************************************************* /*******************************************************************************
* @文件: StellarX.h * @文件: StellarX.h
* @摘要: 星垣(StellarX) GUI框架 - 主包含头文件 * @摘要: 星垣(StellarX) GUI框架 - 主包含头文件
* @版本: v3.0.0 * @版本: v3.0.1
* @描述: * @描述:
* 一个为Windows平台打造的轻量级、模块化C++ GUI框架。 * 一个为Windows平台打造的轻量级、模块化C++ GUI框架。
* 基于EasyX图形库,提供简洁易用的API和丰富的控件。 * 基于EasyX图形库,提供简洁易用的API和丰富的控件。
@@ -12,7 +12,8 @@
* @作者: 我在人间做废物 * @作者: 我在人间做废物
* @邮箱: [3150131407@qq.com] | [ysm3150131407@gmail.com] * @邮箱: [3150131407@qq.com] | [ysm3150131407@gmail.com]
* @官网:https://stellarx-gui.top/ * @官网:https://stellarx-gui.top/
* @仓库: [https://github.com/Ysm-04/StellarX] * @仓库: https://github.com/Ysm-04/StellarX
* @博客:https://blog.stellarx-gui.top/
* *
* @许可证: MIT License * @许可证: MIT License
* @版权: Copyright (c) 2025 我在人间做废物 * @版权: Copyright (c) 2025 我在人间做废物
+8 -4
View File
@@ -21,13 +21,13 @@
#include "CoreTypes.h" #include "CoreTypes.h"
#include "Button.h" #include "Button.h"
#include "Canvas.h" #include "Canvas.h"
#define BUTMINHEIGHT 15 #define BUTMINHEIGHT 15 //页签按钮最小尺寸,过小会导致显示问题
#define BUTMINWIDTH 30 #define BUTMINWIDTH 30 //页签按钮最小尺寸,过小会导致显示问题
class TabControl :public Canvas class TabControl :public Canvas
{ {
int tabBarHeight = BUTMINWIDTH; //页签栏高度 int tabBarHeight = BUTMINWIDTH; //页签栏高度
bool IsFirstDraw = true; //首次绘制标记 bool IsFirstDraw = true; //首次绘制标记
int defaultActivation = -1; //默认激活页签索引 int defaultActivation = -1; //默认激活页签索引
StellarX::TabPlacement tabPlacement = StellarX::TabPlacement::Top; //页签排列方式 StellarX::TabPlacement tabPlacement = StellarX::TabPlacement::Top; //页签排列方式
std::vector<std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>> controls; //页签/页列表 std::vector<std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>> controls; //页签/页列表
@@ -35,6 +35,7 @@ private:
using Canvas::addControl; // 禁止外部误用 using Canvas::addControl; // 禁止外部误用
void addControl(std::unique_ptr<Control>) = delete; // 精准禁用该重载 void addControl(std::unique_ptr<Control>) = delete; // 精准禁用该重载
private: private:
// 初始化页签按钮位置和尺寸
inline void initTabBar(); inline void initTabBar();
inline void initTabPage(); inline void initTabPage();
public: public:
@@ -42,6 +43,7 @@ public:
TabControl(int x, int y, int width, int height); TabControl(int x, int y, int width, int height);
~TabControl(); ~TabControl();
//重写位置设置以适应页签和页面布局
void setX(int x)override; void setX(int x)override;
void setY(int y)override; void setY(int y)override;
@@ -67,6 +69,8 @@ public:
int count() const; int count() const;
//通过页签文本返回索引 //通过页签文本返回索引
int indexOf(const std::string& tabText) const; int indexOf(const std::string& tabText) const;
//设置脏区并请求重绘
void setDirty(bool dirty) override; void setDirty(bool dirty) override;
//请求父控件重绘
void requestRepaint(Control* parent)override; void requestRepaint(Control* parent)override;
}; };
+4 -1
View File
@@ -12,7 +12,9 @@
* - WM_GETMINMAXINFO:按最小“客户区”换算到“窗口矩形”,提供系统层最小轨迹值。 * - WM_GETMINMAXINFO:按最小“客户区”换算到“窗口矩形”,提供系统层最小轨迹值。
* - runEventLoop:只记录 WM_SIZE 的新尺寸;真正绘制放在 needResizeDirty 时集中处理。 * - runEventLoop:只记录 WM_SIZE 的新尺寸;真正绘制放在 needResizeDirty 时集中处理。
*/ */
//fuck windows fuck win32 //fuck windows
//fuck win32
//fuck xiaomi
#pragma once #pragma once
#include "Control.h" #include "Control.h"
@@ -55,6 +57,7 @@ class Window
public: public:
bool dialogClose = false; // 项目内使用的状态位,对话框关闭标志 bool dialogClose = false; // 项目内使用的状态位,对话框关闭标志
mutable bool dialogOpen = false; // 项目内使用的状态位,对话框打开标志
// —— 构造/析构 ——(仅初始化成员;实际样式与子类化在 draw() 中完成) // —— 构造/析构 ——(仅初始化成员;实际样式与子类化在 draw() 中完成)
Window(int width, int height, int mode); Window(int width, int height, int mode);
+2 -11
View File
@@ -313,6 +313,7 @@ bool Button::handleEvent(const ExMessage& msg)
if (hover != oldHover) if (hover != oldHover)
{ {
SX_LOGD("Button") << SX_T("悬停变化: ","hover change: ") << "id=" << id SX_LOGD("Button") << SX_T("悬停变化: ","hover change: ") << "id=" << id
<< " text= " << text
<< " " << (oldHover ? 1 : 0) << "->" << (hover ? 1 : 0); << " " << (oldHover ? 1 : 0) << "->" << (hover ? 1 : 0);
} }
// 处理鼠标点击事件 // 处理鼠标点击事件
@@ -321,7 +322,7 @@ bool Button::handleEvent(const ExMessage& msg)
if (mode == StellarX::ButtonMode::NORMAL) if (mode == StellarX::ButtonMode::NORMAL)
{ {
click = true; click = true;
SX_LOGD("Button") << SX_T("被点击: ","lbtn - down:")<< "id = " << id << " mode = " << (int)mode; SX_LOGD("Button") << SX_T("被点击: ","lbtn - down:")<< "id = " << id <<" text = "<<text << " mode = " << (int)mode;
dirty = true; dirty = true;
consume = true; consume = true;
@@ -620,16 +621,6 @@ StellarX::ControlText Button::getButtonTextStyle() const
return this->textStyle; return this->textStyle;
} }
int Button::getButtonWidth() const
{
return this->width;
}
int Button::getButtonHeight() const
{
return this->height;
}
bool Button::isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius) bool Button::isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius)
{ {
double dis = sqrt(pow(mouseX - x, 2) + pow(mouseY - y, 2)); double dis = sqrt(pow(mouseX - x, 2) + pow(mouseY - y, 2));
+15 -10
View File
@@ -80,12 +80,12 @@ bool Dialog::handleEvent(const ExMessage& msg)
// 如果正在清理或标记为待清理,则不处理事件 // 如果正在清理或标记为待清理,则不处理事件
if (pendingCleanup || isCleaning) if (pendingCleanup || isCleaning)
return false; return false;
// 模态对话框不允许点击外部区域
// 模态对话框:点击对话框外部区域时,发出提示音(\a)并吞噬该事件,不允许操作背景内容。 // 模态对话框:点击对话框外部区域时,发出提示音(\a)并吞噬该事件,不允许操作背景内容。
if (modal && msg.message == WM_LBUTTONUP && if (modal && msg.message == WM_LBUTTONUP &&
(msg.x < x || msg.x > x + width || msg.y < y || msg.y > y + height)) (msg.x < x || msg.x > x + width || msg.y < y || msg.y > y + height))
{ {
std::cout << "\a" << std::endl; std::cout << "\a" << std::endl;
// 模态对话框不允许点击外部区域
return true; return true;
} }
@@ -157,6 +157,8 @@ void Dialog::Show()
close = false; close = false;
shouldClose = false; shouldClose = false;
hWnd.dialogOpen = true;// 通知窗口有对话框打开
if (modal) if (modal)
{ {
// 模态对话框需要阻塞当前线程直到对话框关闭 // 模态对话框需要阻塞当前线程直到对话框关闭
@@ -193,7 +195,7 @@ void Dialog::Show()
setDirty(true); setDirty(true);
} }
// ② 处理这只对话框的鼠标/键盘(沿用原来 EX_MOUSE | EX_KEY // ② 处理这只对话框的鼠标/键盘(沿用原来 EX_MOUSE | EX_KEY
ExMessage msg; ExMessage msg;
if (peekmessage(&msg, EX_MOUSE | EX_KEY)) if (peekmessage(&msg, EX_MOUSE | EX_KEY))
{ {
@@ -274,6 +276,7 @@ void Dialog::initButtons()
okbutton->setOnClickListener([this]() okbutton->setOnClickListener([this]()
{ {
this->SetResult(StellarX::MessageBoxResult::OK); this->SetResult(StellarX::MessageBoxResult::OK);
this->hWnd.dialogClose = true;
this->Close(); }); this->Close(); });
okbutton->textStyle = this->textStyle; okbutton->textStyle = this->textStyle;
@@ -295,7 +298,7 @@ void Dialog::initButtons()
this->Close(); }); this->Close(); });
auto cancelButton = createDialogButton( auto cancelButton = createDialogButton(
(okButton.get()->getX() + okButton.get()->getButtonWidth() + buttonMargin), (okButton.get()->getX() + okButton.get()->getWidth() + buttonMargin),
okButton.get()->getY(), okButton.get()->getY(),
"取消" "取消"
); );
@@ -326,7 +329,7 @@ void Dialog::initButtons()
this->Close(); }); this->Close(); });
auto noButton = createDialogButton( auto noButton = createDialogButton(
(yesButton.get()->getX() + yesButton.get()->getButtonWidth() + buttonMargin), (yesButton.get()->getX() + yesButton.get()->getWidth() + buttonMargin),
yesButton.get()->getY(), yesButton.get()->getY(),
"" ""
); );
@@ -357,7 +360,7 @@ void Dialog::initButtons()
this->Close(); }); this->Close(); });
auto noButton = createDialogButton( auto noButton = createDialogButton(
yesButton.get()->getX() + yesButton.get()->getButtonWidth() + buttonMargin, yesButton.get()->getX() + yesButton.get()->getWidth() + buttonMargin,
yesButton.get()->getY(), yesButton.get()->getY(),
"" ""
); );
@@ -368,7 +371,7 @@ void Dialog::initButtons()
this->Close(); }); this->Close(); });
auto cancelButton = createDialogButton( auto cancelButton = createDialogButton(
noButton.get()->getX() + noButton.get()->getButtonWidth() + buttonMargin, noButton.get()->getX() + noButton.get()->getWidth() + buttonMargin,
noButton.get()->getY(), noButton.get()->getY(),
"取消" "取消"
); );
@@ -401,7 +404,7 @@ void Dialog::initButtons()
this->Close(); }); this->Close(); });
auto cancelButton = createDialogButton( auto cancelButton = createDialogButton(
retryButton.get()->getX() + retryButton.get()->getButtonWidth() + buttonMargin, retryButton.get()->getX() + retryButton.get()->getWidth() + buttonMargin,
retryButton.get()->getY(), retryButton.get()->getY(),
"取消" "取消"
); );
@@ -432,7 +435,7 @@ void Dialog::initButtons()
this->Close(); this->Close();
}); });
auto retryButton = createDialogButton( auto retryButton = createDialogButton(
abortButton.get()->getX() + abortButton.get()->getButtonWidth() + buttonMargin, abortButton.get()->getX() + abortButton.get()->getWidth() + buttonMargin,
abortButton.get()->getY(), abortButton.get()->getY(),
"重试" "重试"
); );
@@ -443,7 +446,7 @@ void Dialog::initButtons()
this->Close(); this->Close();
}); });
auto ignoreButton = createDialogButton( auto ignoreButton = createDialogButton(
retryButton.get()->getX() + retryButton.get()->getButtonWidth() + buttonMargin, retryButton.get()->getX() + retryButton.get()->getWidth() + buttonMargin,
retryButton.get()->getY(), retryButton.get()->getY(),
"忽略" "忽略"
); );
@@ -540,6 +543,8 @@ void Dialog::getTextSize()
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace, settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight, textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
int tempHeight = 0;
int tempWidth = 0;
for (auto& text : lines) for (auto& text : lines)
{ {
int w = textwidth(LPCTSTR(text.c_str())); int w = textwidth(LPCTSTR(text.c_str()));
@@ -549,6 +554,7 @@ void Dialog::getTextSize()
if (this->textWidth < w) if (this->textWidth < w)
this->textWidth = w; this->textWidth = w;
} }
restoreStyle(); restoreStyle();
} }
// 计算逻辑:对话框宽度取【文本区域最大宽度】和【按钮区域总宽度】中的较大值。 // 计算逻辑:对话框宽度取【文本区域最大宽度】和【按钮区域总宽度】中的较大值。
@@ -651,7 +657,6 @@ void Dialog::performDelayedCleanup()
// 所有普通控件 // 所有普通控件
for (auto& c : hWnd.getControls()) c->draw(); for (auto& c : hWnd.getControls()) c->draw();
// 其他对话框(this 已经 show=false,会早退不绘) // 其他对话框(this 已经 show=false,会早退不绘)
// 注意:此处若有容器管理,需要按你的现状遍历 dialogs 再 draw
EndBatchDraw(); EndBatchDraw();
FlushBatchDraw(); FlushBatchDraw();
} }
+1 -4
View File
@@ -117,15 +117,12 @@ namespace StellarX
// - 只影响终端解释输出字节的方式,不影响源码文件编码 // - 只影响终端解释输出字节的方式,不影响源码文件编码
// - 使用 once_flag 避免重复 system 调用造成噪声与性能浪费 // - 使用 once_flag 避免重复 system 调用造成噪声与性能浪费
// //
// 注意:
// - 下面原注释写“切到 UTF-8”,但实际命令是 chcp 936GBK
// - 为避免改动你原注释,这里补充说明事实,保持行为不变
void SxLogger::setGBK() void SxLogger::setGBK()
{ {
#ifdef _WIN32 #ifdef _WIN32
static std::once_flag once; static std::once_flag once;
std::call_once(once, []() { std::call_once(once, []() {
// 切到 UTF-8,避免中文日志在 CP936 控制台下乱码 // 切到chcp 936GBK,避免中文日志在 CP936 控制台下乱码
// 说明:这不是 WinAPI;是执行系统命令 // 说明:这不是 WinAPI;是执行系统命令
std::system("chcp 936 >nul"); std::system("chcp 936 >nul");
+1 -1
View File
@@ -368,7 +368,7 @@ int TabControl::getActiveIndex() const
if (c.first->isClicked()) if (c.first->isClicked())
return idx; return idx;
} }
return idx; return -1;
} }
void TabControl::setActiveIndex(int idx) void TabControl::setActiveIndex(int idx)
+48 -13
View File
@@ -29,19 +29,44 @@ void TextBox::draw()
int text_width = 0; int text_width = 0;
int text_height = 0; int text_height = 0;
std::string pwdText; std::string pwdText;
std::string displayText; // 用于显示的文本(可能被截断)
bool isTextTruncated = false; // 标记文本是否被截断
if (StellarX::TextBoxmode::PASSWORD_MODE == mode) if (StellarX::TextBoxmode::PASSWORD_MODE == mode)
{ {
for (size_t i = 0; i < text.size(); ++i) for (size_t i = 0; i < text.size(); ++i)
pwdText += '*'; pwdText += '*';
text_width = textwidth(LPCTSTR(pwdText.c_str())); displayText = pwdText;
text_height = textheight(LPCTSTR(pwdText.c_str()));
} }
else else
{ {
text_width = textwidth(LPCTSTR(text.c_str())); displayText = text;
text_height = textheight(LPCTSTR(text.c_str()));
} }
// 计算可用宽度(留出左右边距)
int availableWidth = width - 20; // 左右各10像素边距
// 截断文本以适应可用宽度
int currentWidth = textwidth(LPCTSTR(displayText.c_str()));
if (currentWidth > availableWidth && availableWidth > 0)
{
// 需要截断文本,预留空间放置省略号
int ellipsisWidth = textwidth("...");
int truncatedWidth = availableWidth - ellipsisWidth;
std::string truncatedText = displayText;
while (truncatedText.size() > 0 && textwidth(LPCTSTR(truncatedText.c_str())) > truncatedWidth)
{
truncatedText.pop_back();
}
displayText = truncatedText + "...";
isTextTruncated = true;
currentWidth = textwidth(LPCTSTR(displayText.c_str()));
}
text_width = currentWidth;
text_height = textheight(LPCTSTR(displayText.c_str()));
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage) if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
saveBackground(this->x, this->y, this->width, this->height); saveBackground(this->x, this->y, this->width, this->height);
// 恢复背景(清除旧内容) // 恢复背景(清除旧内容)
@@ -51,23 +76,19 @@ void TextBox::draw()
{ {
case StellarX::ControlShape::RECTANGLE: case StellarX::ControlShape::RECTANGLE:
fillrectangle(x, y, x + width, y + height);//有边框填充矩形 fillrectangle(x, y, x + width, y + height);//有边框填充矩形
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(displayText.c_str()));
: outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
break; break;
case StellarX::ControlShape::B_RECTANGLE: case StellarX::ControlShape::B_RECTANGLE:
solidrectangle(x, y, x + width, y + height);//无边框填充矩形 solidrectangle(x, y, x + width, y + height);//无边框填充矩形
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(displayText.c_str()));
: outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
break; break;
case StellarX::ControlShape::ROUND_RECTANGLE: case StellarX::ControlShape::ROUND_RECTANGLE:
fillroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);//有边框填充圆角矩形 fillroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);//有边框填充圆角矩形
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(displayText.c_str()));
:outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
break; break;
case StellarX::ControlShape::B_ROUND_RECTANGLE: case StellarX::ControlShape::B_ROUND_RECTANGLE:
solidroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);//无边框填充圆角矩形 solidroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);//无边框填充圆角矩形
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(displayText.c_str()));
:outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
break; break;
} }
restoreStyle(); restoreStyle();
@@ -197,10 +218,24 @@ void TextBox::setTextBoxBk(COLORREF color)
void TextBox::setText(std::string text) void TextBox::setText(std::string text)
{ {
if(text == this->text)
return; // 文本未改变,无需更新和重绘
if (text.size() > maxCharLen) if (text.size() > maxCharLen)
text = text.substr(0, maxCharLen); text = text.substr(0, maxCharLen);
this->text = text; this->text = text;
this->dirty = true; this->dirty = true; // 标记需要重绘,不论是否窗口图形上下文是否已初始化,等第一次绘制时由窗口真正调用 draw() 来重绘显示文本
//有父控件时请求父控件重绘,无父控件时直接重绘,确保文本更新后界面正确刷新显示
if (nullptr != parent)
{
//通过hasSnap是否持有有效快照,判断控件是否已经绘制过,避免在控件未绘制前/窗口图形上下文未初始化调用draw()导致的错误
if (hasSnap)
requestRepaint(parent);
}
else
if (hasSnap)
draw();
} }
std::string TextBox::getText() const std::string TextBox::getText() const
+53 -25
View File
@@ -193,9 +193,10 @@ LRESULT CALLBACK Window::WndProcThunk(HWND h, UINT m, WPARAM w, LPARAM l)
return TRUE; return TRUE;
} }
ApplyMinSizeOnSizing(prc, w, h, self->minClientW, self->minClientH);
RECT before = *prc;// 记录调整前矩形以便日志输出 RECT before = *prc;// 记录调整前矩形以便日志输出
if (before.left != prc->left || before.top != prc->top || before.right != prc->right || before.bottom != prc->bottom) ApplyMinSizeOnSizing(prc, w, h, self->minClientW, self->minClientH);
//if (before.left != prc->left || before.top != prc->top || before.right != prc->right || before.bottom != prc->bottom)
if (memcmp(&before, prc, sizeof(RECT)) != 0)
{ {
SX_LOGD("Resize") SX_LOGD("Resize")
<< SX_T("WM_SIZING 夹具:","WM_SIZING clamp: ") << SX_T("WM_SIZING 夹具:","WM_SIZING clamp: ")
@@ -372,7 +373,8 @@ int Window::runEventLoop()
while (running) while (running)
{ {
bool consume = false; bool consume = false; // 事件是否被消费的标志(用于输入事件分发)
bool redrawDialogs = false; // 是否需要重绘对话框(控件事件可能引起对话框状态变化)
if (peekmessage(&msg, EX_MOUSE | EX_KEY | EX_WINDOW, true)) if (peekmessage(&msg, EX_MOUSE | EX_KEY | EX_WINDOW, true))
{ {
@@ -422,9 +424,7 @@ int Window::runEventLoop()
{ {
auto& d = *it; auto& d = *it;
if (d->IsVisible() && !d->model()) if (d->IsVisible() && !d->model())
{
consume = d->handleEvent(msg); consume = d->handleEvent(msg);
}
if (consume) if (consume)
{ {
SX_LOGD("Event") << SX_T("事件被非模态对话框处理","Event consumed by non-modal dialog"); SX_LOGD("Event") << SX_T("事件被非模态对话框处理","Event consumed by non-modal dialog");
@@ -433,44 +433,66 @@ int Window::runEventLoop()
} }
if (!consume) if (!consume)
{ {
for (auto& c : controls) for (auto it = controls.rbegin(); it != controls.rend(); ++it)
{ {
consume = c->handleEvent(msg); consume = (*it)->handleEvent(msg);
if (consume) if (consume)
{ {
SX_LOGD("Event") << SX_T("事件被控件处理 id=","Event consumed by control id=") << c->getId(); SX_LOGD("Event") << SX_T("事件被控件处理 id=", "Event consumed by control id=") << (*it)->getId();
redrawDialogs = true; // 控件事件可能引起对话框状态变化,标记需要重绘对话框
break; break;
} }
} }
} }
} }
// 关键点⑦:事件处理后,如果对话框状态可能变化(例如控件事件引起的控件重绘可能导致对话框被覆盖),优先重绘对话框以确保界面响应及时;后续再根据 needResizeDirty 进行一次性重绘。
if (redrawDialogs)
{
for (auto& d : dialogs)
{
if (!d->model() && d->IsVisible())
d->setDirty(true);
d->draw();
}
redrawDialogs = false; // 重置标志
}
//如果有对话框打开或者关闭强制重绘 //如果有对话框打开或者关闭强制重绘
bool needredraw = false; bool needredraw = false;
for (auto& d : dialogs) if(dialogOpen)
{ {
needredraw = d->IsVisible(); for (auto& d : dialogs)
if (needredraw)break; {
needredraw = d->IsVisible();
if (needredraw)break;
}
} }
if (needredraw || dialogClose) if (needredraw || dialogClose)
{ {
// 对话框关闭后,需要手动合成一个鼠标移动消息并分发给所有普通控件, if (dialogClose)
// 以便它们能及时更新悬停状态(hover),否则悬停状态可能保持错误状态。
// 先把当前鼠标位置转换为客户区坐标,并合成一次 WM_MOUSEMOVE,先分发给控件更新 hover 状态
POINT pt;
if (GetCursorPos(&pt))
{ {
ScreenToClient(this->hWnd, &pt); // 对话框关闭后,需要手动合成一个鼠标移动消息并分发给所有普通控件,
ExMessage mm; // 以便它们能及时更新悬停状态(hover),否则悬停状态可能保持错误状态。
mm.message = WM_MOUSEMOVE; // 先把当前鼠标位置转换为客户区坐标,并合成一次 WM_MOUSEMOVE,先分发给控件更新 hover 状态
mm.x = (short)pt.x; SX_LOGD("Event") << SX_T("对话框关闭,合成WM_MOUSEMOVE已下发", "Dialog closed; synthetic WM_MOUSEMOVE dispatched");
mm.y = (short)pt.y; POINT pt;
// 只分发给 window 层控件(因为 dialog 已经关闭或即将关闭) if (GetCursorPos(&pt))
for (auto& c : controls) {
c->handleEvent(mm); ScreenToClient(this->hWnd, &pt);
ExMessage mm;
mm.message = WM_MOUSEMOVE;
mm.x = (short)pt.x;
mm.y = (short)pt.y;
// 只分发给 window 层控件(因为 dialog 已经关闭或即将关闭)
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
(*it)->handleEvent(mm);
}
dialogClose = false; // 重置标志
} }
BeginBatchDraw(); BeginBatchDraw();
SX_LOGD("Event") << SX_T("对话框打开/关闭,触发全量重绘", "The dialog box opens/closes, triggering a full redraw");
// 先绘制普通控件 // 先绘制普通控件
for (auto& c : controls) for (auto& c : controls)
c->draw(); c->draw();
@@ -483,6 +505,8 @@ int Window::runEventLoop()
} }
EndBatchDraw(); EndBatchDraw();
needredraw = false; needredraw = false;
dialogOpen = false;
} }
// —— 统一收口(needResizeDirty 为真时执行一次性重绘)—— // —— 统一收口(needResizeDirty 为真时执行一次性重绘)——
if (needResizeDirty) if (needResizeDirty)
@@ -655,7 +679,10 @@ bool Window::hasNonModalDialogWithCaption(const std::string& caption, const std:
if (!dptr) continue; if (!dptr) continue;
if (auto* d = dynamic_cast<Dialog*>(dptr.get())) if (auto* d = dynamic_cast<Dialog*>(dptr.get()))
if (d->IsVisible() && !d->model() && d->GetCaption() == caption && d->GetText() == message) if (d->IsVisible() && !d->model() && d->GetCaption() == caption && d->GetText() == message)
{
dialogOpen = true;
return true; return true;
}
} }
return false; return false;
} }
@@ -853,4 +880,5 @@ void Window::adaptiveLayout(std::unique_ptr<Control>& c, const int finalH, const
} }
} }
c->onWindowResize(); c->onWindowResize();
} }
File diff suppressed because it is too large Load Diff