Compare commits
6 Commits
v2.3.1
...
10a91f9a64
| Author | SHA1 | Date | |
|---|---|---|---|
| 10a91f9a64 | |||
| eb96e5a64e | |||
| 5cb59b3652 | |||
| 0c1cf2938f | |||
| aa0fa8d320 | |||
| 43564ef675 |
@@ -7,9 +7,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
[中文文档](CHANGELOG.md)
|
[中文文档](CHANGELOG.md)
|
||||||
|
|
||||||
|
## [v3.0.0] - 2026-01-09
|
||||||
|
|
||||||
|
### ✨ New Features
|
||||||
|
|
||||||
|
- **Logging System Demo**: Located in: examples\SXLog-LoggingSystemDemo
|
||||||
|
- **Lightweight Logging System SxLog (SxLogger / SxLogLine / SxLogScope / Sink / TagFilter / LanguageSwitch):** A unified logging entry and macro encapsulation is introduced, supporting level and tag-based filtering, console/file output, and optional file rolling based on size thresholds. It also provides bilingual text selection capabilities (via `SX_T` / `SxT`). The logging macros have a short-circuit mechanism: logs will not be constructed or string concatenated if the level or tag condition is not met. Output is serialized with mutex to ensure thread safety. The module does not depend on WinAPI debug output channels and does not introduce third-party libraries.
|
||||||
|
- Typical usage: `SX_LOGD/SX_LOGI/SX_LOGW/SX_LOGE/SX_LOGF/SX_LOG_TRACE` with stream concatenation; `SX_TRACE_SCOPE` for scope timing.
|
||||||
|
- Core configuration: `setMinLevel(...)`, `setTagFilter(...)`, `enableConsole(true/false)`, `enableFile(path, append, rotateBytes)`, `setLanguage(ZhCN/EnUS)`, `setGBK()`
|
||||||
|
|
||||||
|
### ⚙️ Changes
|
||||||
|
|
||||||
|
- **TabControl Tab Switching Logic Adjustment:** The logic for tab button switching has been modified to “first close the currently opened tab, then open the triggered tab,” avoiding potential timing issues caused by the “open first, close later” sequence in complex containers/snapshot chains. The external API remains unchanged (involving the internal switching logic of `TabControl::add`).
|
||||||
|
- **Setter Semantics Refined for Controls:** The responsibility of setters is clarified to be “updating state and marking as dirty,” with drawing now uniformly triggered by the window/container's redraw mechanism, reducing lifecycle coupling and snapshot pollution risks (see related fix entries below).
|
||||||
|
|
||||||
|
### ✅ Fixes
|
||||||
|
|
||||||
|
- **TextBox::setText Causes Interruptions Before Entering Event Loop:** Fixed the issue where calling `TextBox::setText()` before window initialization (before `initgraph()` or when the graphical context is not ready) caused access conflicts and crashes. The previous implementation coupled “state updates” with “immediate drawing,” leading to `setText()` internally triggering `draw()`, which crashed when there was no graphics context (e.g., `saveBackground()/getimage()`). Now, the setter only updates the text and marks the control as dirty, with the drawing handled by the unified redraw flow.
|
||||||
|
- **TabControl Switching and Table Content Overlap (Ghosting):** Fixed a stable ghosting issue when switching tabs or resetting the table data. The issue was caused by partial redraws happening before the snapshot was ready, leading to snapshot contamination. This was addressed by rolling back the behavior of `setIsVisible(true)` immediately triggering `requestRepaint`, and instead marking as dirty while adding safeguards to `Canvas::requestRepaint` and `TabControl::requestRepaint`: when the container is `dirty`, `hasSnap=0`, or the snapshot cache is invalid, partial fast-path is disabled and full redraw is triggered to ensure correct snapshot chains.
|
||||||
|
|
||||||
|
### ⚠️ Breaking Changes
|
||||||
|
|
||||||
|
- **Removal of Button Dimension Alias APIs:** Removed `Button::getButtonWidth()` and `Button::getButtonHeight()`, unifying the control's dimension APIs under the base class `Control::getWidth()` and `Control::getHeight()`. This change will break backward compatibility if the old methods were used, but the behavior (retrieving `width/height`) remains the same.
|
||||||
|
- **Removal of Immediate Refresh Side Effects in Setters:** The side effect of immediate drawing in setters like `setIsVisible(true)` and `setText()` has been removed. If previous business code relied on "immediate visibility update after calling," you will now need to ensure a subsequent redraw path (via window's main loop/container redraw or explicit refresh) for visual updates to be completed.
|
||||||
|
|
||||||
|
### 📌 Upgrade Instructions
|
||||||
|
|
||||||
|
1. **Button Dimension API Migration:**
|
||||||
|
- `getButtonWidth()` → `getWidth()`
|
||||||
|
- `getButtonHeight()` → `getHeight()`
|
||||||
|
2. **Adapting to Setters No Longer Triggering Immediate Drawing:**
|
||||||
|
- It is now possible to set properties (like `TextBox::setText("default")`) during initialization, with visual updates being handled by the first `Window::draw()` or the main event loop.
|
||||||
|
- If you need immediate visual updates in non-event-driven scenarios, you must ensure that a redraw path is triggered (avoid manually calling `draw()` inside the setter, as this would cause lifecycle coupling).
|
||||||
|
3. **SxLog Integration Suggestions:**
|
||||||
|
- Ensure basic configuration at the program entry (console output/minimum level/language), and use consistent tags (such as `Dirty/Resize/Table/Canvas/TabControl`) to establish traceable event chains; for high-frequency paths, control noise and I/O costs using level and tag filtering.
|
||||||
|
|
||||||
|
## [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
|
## [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))
|
- 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.
|
- 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.
|
||||||
|
|||||||
59
CHANGELOG.md
59
CHANGELOG.md
@@ -3,13 +3,68 @@
|
|||||||
StellarX 项目所有显著的变化都将被记录在这个文件中。
|
StellarX 项目所有显著的变化都将被记录在这个文件中。
|
||||||
|
|
||||||
格式基于 [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
格式基于 [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
并且本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
并且本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)
|
||||||
|
|
||||||
[English document](CHANGELOG.en.md)
|
[English document](CHANGELOG.en.md)
|
||||||
|
|
||||||
|
## [v3.0.0] - 2026 - 01 - 09
|
||||||
|
|
||||||
|
### ✨ 新增
|
||||||
|
|
||||||
|
- **日志系统使用Demo** 在:examples\SXLog-日志系统使用demo
|
||||||
|
|
||||||
|
- **轻量日志系统 SxLog(SxLogger / SxLogLine / SxLogScope / Sink / TagFilter / LanguageSwitch):**新增统一日志入口与宏封装,支持按级别与 Tag 筛选输出,支持控制台/文件落地与可选滚动(按大小阈值),并提供中英文双语文本选择能力(`SX_T` / `SxT`)。日志宏具备短路机制:未命中级别或 Tag 时不构造日志对象、不拼接字符串;输出侧以行级互斥保证多线程下不交错。该模块不依赖 WinAPI 调试输出通道、不引入第三方库。
|
||||||
|
- 典型用法:`SX_LOGD/SX_LOGI/SX_LOGW/SX_LOGE/SX_LOGF/SX_LOG_TRACE` + 流式拼接;`SX_TRACE_SCOPE` 作用域耗时统计
|
||||||
|
- 核心配置:`setMinLevel(...)`、`setTagFilter(...)`、`enableConsole(true/false)`、`enableFile(path, append, rotateBytes)`、`setLanguage(ZhCN/EnUS)`、`setGBK()`
|
||||||
|
|
||||||
|
### ⚙️ 变更
|
||||||
|
|
||||||
|
- **TabControl 页签切换时序调整:**修改页签按钮切换逻辑为“先关闭当前已打开页,再打开目标页”,避免“先打开再关闭”在复杂容器/快照链路下引入的时序不确定性;对外 API 不变(涉及 `TabControl::add` 内部切换逻辑)。
|
||||||
|
- **控件 Setter 语义收敛:**明确控件 Setter 的职责为“更新状态并标脏”,绘制统一由窗口/容器的重绘收口机制触发,降低生命周期耦合与快照污染风险(见下方相关修复条目)。
|
||||||
|
|
||||||
|
### ✅ 修复
|
||||||
|
|
||||||
|
- **TextBox::setText 在进入事件循环前调用触发中断:**修复在窗口尚未初始化(未 `initgraph()`、图形上下文未就绪)前调用 `TextBox::setText()` 导致访问冲突崩溃的问题。旧实现将“状态更新”和“立即绘制”耦合,`setText()` 内部直接触发 `draw()`,进而在无图形上下文时进入 `saveBackground()/getimage()` 路径崩溃;现已移除 Setter 内直接绘制,仅保留赋值与置脏,绘制交由统一重绘流程完成。
|
||||||
|
- **TabControl 切换/关闭后 Table 新旧内容重叠(重影):**修复页签切换与表格重置数据后出现的稳定残影问题。根因是“快照未就绪阶段发生局部重绘”导致快照污染;本次回退 `setIsVisible(true)` 的“立即向上 requestRepaint”行为,改为仅置脏,并为 `Canvas::requestRepaint` 与 `TabControl::requestRepaint` 增加护栏:当容器 `dirty` 或 `hasSnap=0` 或快照缓存无效时,禁止 partial fast-path,自动降级为全量重绘以保证快照链路正确性;同时修正 `Table::setData(...)` 列补齐边界问题,降低异常噪声。
|
||||||
|
|
||||||
|
### ⚠️ 可能不兼容
|
||||||
|
|
||||||
|
- **删除 Button 尺寸别名 API(Breaking):**移除 `Button::getButtonWidth()` / `Button::getButtonHeight()`,统一使用基类 `Control::getWidth()` / `Control::getHeight()` 获取控件尺寸。该改动会导致旧代码在升级到 v3.0.0 后编译失败,但行为语义保持一致(仍读取同一 `width/height`)。
|
||||||
|
- **可见性/文本设置的“即时刷新”副作用移除:**`setIsVisible(true)` 与 `setText()` 等 Setter 不再保证立刻触发绘制;如业务代码此前依赖“调用后立即可见”,需要确保后续存在一次重绘收口(窗口主循环/容器重绘/显式刷新)以完成视觉更新。
|
||||||
|
|
||||||
|
### 📌 升级指引
|
||||||
|
|
||||||
|
1. **Button 宽高接口迁移:**
|
||||||
|
- `getButtonWidth()` → `getWidth()`
|
||||||
|
- `getButtonHeight()` → `getHeight()`
|
||||||
|
2. **Setter 不再“立即绘制”的适配:**
|
||||||
|
- 初始化阶段可先设置属性(如 `TextBox::setText("default")`),由首次 `Window::draw()` / 主事件循环的统一绘制完成可视刷新;
|
||||||
|
- 若在非事件驱动场景下程序化更新后需要立刻刷新,请确保触发一次统一重绘路径(避免在 Setter 内手动调用 `draw()` 造成生命周期耦合)。
|
||||||
|
3. **SxLog 接入建议:**
|
||||||
|
- 在程序入口完成基础配置(控制台输出/最低级别/语言),并使用统一 Tag(如 `Dirty/Resize/Table/Canvas/TabControl`)建立可回放的事件链路;高频路径建议通过级别与 Tag 控制噪声与 I/O 成本。
|
||||||
|
|
||||||
|
## [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
|
## [v2.3.1] - 2025 - 11 - 30
|
||||||
|
|
||||||
## 🙏 鸣谢
|
### 🙏 鸣谢
|
||||||
|
|
||||||
- 感谢用户 [@To-KongBai](https://github.com/To-KongBai) 提供稳定复现步骤与关键现象对比(容器嵌套孙控件坐标转换问题),帮助我们快速确认多容器嵌套时的控件坐标转换问题并修复。([Issues#6](https://github.com/Ysm-04/StellarX/issues/6))
|
- 感谢用户 [@To-KongBai](https://github.com/To-KongBai) 提供稳定复现步骤与关键现象对比(容器嵌套孙控件坐标转换问题),帮助我们快速确认多容器嵌套时的控件坐标转换问题并修复。([Issues#6](https://github.com/Ysm-04/StellarX/issues/6))
|
||||||
- 在即将上线的官网中(ICP备案中)我们计划加入一个贡献者鸣谢墙,欢迎各位用户反馈BUG或者分享自己用星垣做的界面,我们将认真阅读并收录鸣谢
|
- 在即将上线的官网中(ICP备案中)我们计划加入一个贡献者鸣谢墙,欢迎各位用户反馈BUG或者分享自己用星垣做的界面,我们将认真阅读并收录鸣谢
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
33
README.en.md
33
README.en.md
@@ -7,8 +7,8 @@
|
|||||||

|

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

|

|
||||||

|

|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
@@ -25,29 +25,34 @@ This is a **teaching-grade and tooling-grade** framework that helps developers u
|
|||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## 🆕 V2.3.0 - Major Update
|
### 🆕V3.0.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.**
|
[CHANGELOG.en.md](CHANGELOG.en.md)
|
||||||
|
|
||||||
- **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 Features
|
||||||
|
|
||||||
- **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.
|
- **SxLog**: A lightweight logging system with support for log levels, tag filtering, bilingual (Chinese/English) output, and console/file logging with optional file rolling.
|
||||||
|
- **TabControl**: Improved tab switching logic to ensure the current tab is closed before the target tab is opened.
|
||||||
|
- **Improved Setters**: Setters now only update state and mark as dirty, with drawing handled by the unified redraw flow.
|
||||||
|
|
||||||
- **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.
|
### ⚙️ Changes
|
||||||
|
|
||||||
- **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.
|
- **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.
|
||||||
|
|
||||||
- **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.
|
### ✅ Fixes
|
||||||
|
|
||||||
- **Additional Fixes**: Corrected delayed background snapshot updates in tables and dialogs under certain edge cases.
|
- **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.
|
||||||
|
|
||||||
For details, please refer to the [CHANGELOG.en](CHANGELOG.en.md).
|
### 📌 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
|
||||||
|
|
||||||
|
|||||||
40
README.md
40
README.md
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
[English document](README.en.md)
|
[English document](README.en.md)
|
||||||
|
|
||||||
|
官网地址:https://stellarx-gui.top
|
||||||
|
|
||||||
> 本仓库为 **StellarX** 主仓:开发与 Issue/PR 均在 GitHub 进行。
|
> 本仓库为 **StellarX** 主仓:开发与 Issue/PR 均在 GitHub 进行。
|
||||||
> GitCode 仅为只读镜像:如需反馈请到 GitHub:https://github.com/Ysm-04/StellarX
|
> GitCode 仅为只读镜像:如需反馈请到 GitHub:https://github.com/Ysm-04/StellarX
|
||||||
|
|
||||||
@@ -12,8 +14,8 @@
|
|||||||

|

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

|

|
||||||

|

|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
@@ -30,30 +32,36 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ==公告==
|
|
||||||
|
|
||||||
## 🙏 鸣谢
|
|
||||||
|
|
||||||
- 感谢用户 [@To-KongBai](https://github.com/To-KongBai) 提供稳定复现步骤与关键现象对比(容器嵌套孙控件坐标转换问题),帮助我们快速确认多容器嵌套时的控件坐标转换问题并修复。([Issues#6](https://github.com/Ysm-04/StellarX/issues/6))
|
### 🆕V3.0.0 - 重要更新
|
||||||
- 在即将上线的官网中(ICP备案中)我们计划加入一个贡献者鸣谢墙,欢迎各位用户反馈BUG或者分享自己用星垣做的界面,我们将认真阅读并收录鸣谢
|
|
||||||
- 真诚的感谢每一位反馈BUG的用户,你们的反馈将使星垣更加稳定和健壮
|
|
||||||
|
|
||||||
---
|
完整版建议查看[更新日志](CHANGELOG.md)
|
||||||
|
|
||||||
## 🆕V2.3.1——重要更新
|
### ✨ 新增功能
|
||||||
|
|
||||||
### ✨ 新增
|
- **SxLog**: 轻量级日志系统,支持日志级别、标签过滤、中英文输出及控制台/文件日志,支持文件滚动。
|
||||||
|
- **TabControl**: 改进页签切换逻辑,确保先关闭当前页再打开目标页。
|
||||||
新增一个登录界面Demo在主仓库**examples/**目录下
|
- **控件 Setter 改进**: Setter 仅更新状态并标记为脏,绘制由统一重绘流程处理。
|
||||||
|
|
||||||
### ⚙️ 变更
|
### ⚙️ 变更
|
||||||
|
|
||||||
- **Dialog背景快照机制:**`Dialog`不在自己抓取和销毁快照,**删除**重载的抓取和恢复快照的方法,完全交由基类`Canvas`处理,`Dialog`的`draw`方法中不在处理快照
|
- **TabControl 默认激活页**: 默认激活页逻辑明确,首次设置激活索引后才绘制。
|
||||||
- **窗口变化重绘时控件和窗口重绘的时机调整:**主事件循环中窗口大小发生变化时先处理控件尺寸,并回贴和释放旧快照,然后再重绘新尺寸窗口,最后绘制控件
|
|
||||||
|
|
||||||
本次针对用户反馈和已知内容进行了一些修复……
|
### ✅ 修复
|
||||||
|
|
||||||
详细变更请参阅[更新日志](CHANGELOG.md)
|
- **TabControl::setActiveIndex 崩溃**: 修复首次绘制前设置默认激活页时崩溃的问题。
|
||||||
|
- **TabControl 渲染错乱**: 修复 `setIsVisible` 切换时,非激活页错误绘制导致的重叠问题。
|
||||||
|
|
||||||
|
### ⚠️ 破坏性更改
|
||||||
|
|
||||||
|
- **Button 尺寸 API 移除**: 移除了 `getButtonWidth()` / `getButtonHeight()`,请使用 `getWidth()` / `getHeight()`。
|
||||||
|
- **Setter 不再即时绘制**: `setText()` 等 Setter 不再触发即时绘制。
|
||||||
|
|
||||||
|
### 📌 升级指南
|
||||||
|
|
||||||
|
- **Button**: 将 `getButtonWidth()` / `getButtonHeight()` 替换为 `getWidth()` / `getHeight()`。
|
||||||
|
- **Setter**: 在调用 Setter 后确保触发重绘机制。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
58
examples/SXLog-日志系统使用demo/SXLog-demo.cpp
Normal file
58
examples/SXLog-日志系统使用demo/SXLog-demo.cpp
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#include"StellarX.h"//包含SXLog头文件
|
||||||
|
|
||||||
|
#if 1
|
||||||
|
// 业务场景只需用日志系统时的最简用法示例
|
||||||
|
/*
|
||||||
|
//设置编码 按需开启
|
||||||
|
StellarX::SxLogger::setGBK();
|
||||||
|
|
||||||
|
//启用日志文件和控制台输出 按需开启
|
||||||
|
StellarX::SxLogger::Get().enableFile("stellarx.log", false, 1024);
|
||||||
|
StellarX::SxLogger::Get().enableConsole(true);
|
||||||
|
|
||||||
|
//设置最低日志级别和语言 按需设置
|
||||||
|
StellarX::SxLogger::Get().setMinLevel(StellarX::SxLogLevel::Debug); // Info/Debug/Trace 自己切
|
||||||
|
StellarX::SxLogger::Get().setLanguage(StellarX::SxLogLanguage::ZhCN); // ZhCN / EnUS
|
||||||
|
*/
|
||||||
|
using namespace StellarX;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
|
||||||
|
// 1) 可选:把 Windows 控制台切到 GBK,避免中文乱码(内部用 system("chcp 936"))
|
||||||
|
SxLogger::setGBK();
|
||||||
|
|
||||||
|
// 2) 获取全局 logger 并开启控制台输出
|
||||||
|
SxLogger& log = SxLogger::Get();
|
||||||
|
log.enableConsole(true);
|
||||||
|
|
||||||
|
// 3) 最低输出级别(Debug 及以上都会输出)
|
||||||
|
log.setMinLevel(SxLogLevel::Debug);
|
||||||
|
|
||||||
|
// 4) 语言选择(影响 SX_T 的文本选择,不做转码)
|
||||||
|
log.setLanguage(SxLogLanguage::ZhCN);
|
||||||
|
|
||||||
|
// 5) 打几条日志(流式拼接)
|
||||||
|
SX_LOGI("Init") << SX_T("日志系统已启用", "logging enabled");
|
||||||
|
SX_LOGD("Init") << "minLevel=" << (int)log.getMinLevel();
|
||||||
|
|
||||||
|
// 6) 作用域耗时统计(只有 Trace 级别打开且 shouldLog 命中才会输出)
|
||||||
|
{
|
||||||
|
SX_TRACE_SCOPE("Perf", "demo-scope");
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7) 切换语言演示
|
||||||
|
log.setLanguage(SxLogLanguage::EnUS);
|
||||||
|
SX_LOGI("Init") << SX_T("这条不会显示中文", "this line is English");
|
||||||
|
|
||||||
|
// 8) Tag 过滤演示(只允许 Init)
|
||||||
|
log.setTagFilter(SxTagFilterMode::Whitelist, { "Init" });
|
||||||
|
SX_LOGI("Init") << "allowed";
|
||||||
|
SX_LOGI("Table") << "blocked (should not appear)";
|
||||||
|
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -24,7 +24,7 @@ int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _
|
|||||||
// 3. 为按钮设置点击事件(使用Lambda表达式)
|
// 3. 为按钮设置点击事件(使用Lambda表达式)
|
||||||
myButton->setOnClickListener([&mainWindow]() {
|
myButton->setOnClickListener([&mainWindow]() {
|
||||||
// 使用消息框工厂创建模态对话框
|
// 使用消息框工厂创建模态对话框
|
||||||
auto result = StellarX::MessageBox::ShowModal(
|
auto result = StellarX::MessageBox::showModal(
|
||||||
mainWindow,
|
mainWindow,
|
||||||
"欢迎使用星垣GUI\r\n作者:我在人间做废物",
|
"欢迎使用星垣GUI\r\n作者:我在人间做废物",
|
||||||
"问候",
|
"问候",
|
||||||
|
|||||||
@@ -21,177 +21,170 @@
|
|||||||
#include "Control.h"
|
#include "Control.h"
|
||||||
#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
|
||||||
{
|
{
|
||||||
|
std::string text; // 按钮上的文字
|
||||||
|
bool click; // 是否被点击
|
||||||
|
bool hover; // 是否被悬停
|
||||||
|
|
||||||
std::string text; // 按钮上的文字
|
std::string cutText; // 切割后的文本
|
||||||
bool click; // 是否被点击
|
bool needCutText = true; // 是否需要切割文本
|
||||||
bool hover; // 是否被悬停
|
bool isUseCutText = false; // 是否使用切割文本
|
||||||
|
int padX = TEXTMARGINS_X; // 文本最小左右内边距
|
||||||
|
int padY = TEXTMARGINS_Y; // 文本最小上下内边距
|
||||||
|
|
||||||
std::string cutText; // 切割后的文本
|
COLORREF buttonTrueColor; // 按钮被点击后的颜色
|
||||||
bool needCutText = true; // 是否需要切割文本
|
COLORREF buttonFalseColor; // 按钮未被点击的颜色
|
||||||
bool isUseCutText = false; // 是否使用切割文本
|
COLORREF buttonHoverColor; // 按钮被鼠标悬停的颜色
|
||||||
int padX = TEXTMARGINS_X; // 文本最小左右内边距
|
COLORREF buttonBorderColor = RGB(0, 0, 0);// 按钮边框颜色
|
||||||
int padY = TEXTMARGINS_Y; // 文本最小上下内边距
|
|
||||||
|
|
||||||
COLORREF buttonTrueColor; // 按钮被点击后的颜色
|
StellarX::ButtonMode mode; // 按钮模式
|
||||||
COLORREF buttonFalseColor; // 按钮未被点击的颜色
|
StellarX::ControlShape shape; // 按钮形状
|
||||||
COLORREF buttonHoverColor; // 按钮被鼠标悬停的颜色
|
|
||||||
COLORREF buttonBorderColor = RGB(0,0,0);// 按钮边框颜色
|
|
||||||
|
|
||||||
StellarX::ButtonMode mode; // 按钮模式
|
StellarX::FillMode buttonFillMode = StellarX::FillMode::Solid; //按钮填充模式
|
||||||
StellarX::ControlShape shape; // 按钮形状
|
StellarX::FillStyle buttonFillIma = StellarX::FillStyle::BDiagonal; //按钮填充图案
|
||||||
|
IMAGE* buttonFileIMAGE = nullptr; //按钮填充图像
|
||||||
|
|
||||||
StellarX::FillMode buttonFillMode = StellarX::FillMode::Solid; //按钮填充模式
|
std::function<void()> onClickCallback; //回调函数
|
||||||
StellarX::FillStyle buttonFillIma = StellarX::FillStyle::BDiagonal; //按钮填充图案
|
std::function<void()> onToggleOnCallback; //TOGGLE模式下的回调函数
|
||||||
IMAGE* buttonFileIMAGE = nullptr; //按钮填充图像
|
std::function<void()> onToggleOffCallback; //TOGGLE模式下的回调函数
|
||||||
|
|
||||||
|
StellarX::ControlText oldStyle = textStyle; // 按钮文字样式
|
||||||
|
int oldtext_width = -1;
|
||||||
|
int oldtext_height = -1;
|
||||||
|
int text_width = 0;
|
||||||
|
int text_height = 0;
|
||||||
|
|
||||||
|
// === Tooltip ===
|
||||||
|
bool tipEnabled = false; // 是否启用
|
||||||
|
bool tipVisible = false; // 当前是否显示
|
||||||
|
bool tipFollowCursor = false; // 是否跟随鼠标
|
||||||
|
bool tipUserOverride = false; // 是否用户自定义了tip文本
|
||||||
|
int tipDelayMs = 1000; // 延时(毫秒)
|
||||||
|
int tipOffsetX = 12; // 相对鼠标偏移
|
||||||
|
int tipOffsetY = 18;
|
||||||
|
ULONGLONG tipHoverTick = 0; // 开始悬停的时间戳
|
||||||
|
int lastMouseX = 0; // 最新鼠标位置(用于定位)
|
||||||
|
int lastMouseY = 0;
|
||||||
|
|
||||||
std::function<void()> onClickCallback; //回调函数
|
std::string tipTextClick; //NORMAL 模式下用
|
||||||
std::function<void()> onToggleOnCallback; //TOGGLE模式下的回调函数
|
std::string tipTextOn; // click==true 时用
|
||||||
std::function<void()> onToggleOffCallback; //TOGGLE模式下的回调函数
|
std::string tipTextOff; // click==false 时用
|
||||||
|
Label tipLabel; // 直接复用Label作为提示
|
||||||
StellarX::ControlText oldStyle = textStyle; // 按钮文字样式
|
|
||||||
int oldtext_width = -1;
|
|
||||||
int oldtext_height = -1;
|
|
||||||
int text_width = 0;
|
|
||||||
int text_height = 0;
|
|
||||||
|
|
||||||
// === Tooltip ===
|
|
||||||
bool tipEnabled = false; // 是否启用
|
|
||||||
bool tipVisible = false; // 当前是否显示
|
|
||||||
bool tipFollowCursor = false; // 是否跟随鼠标
|
|
||||||
bool tipUserOverride = false; // 是否用户自定义了tip文本
|
|
||||||
int tipDelayMs = 1000; // 延时(毫秒)
|
|
||||||
int tipOffsetX = 12; // 相对鼠标偏移
|
|
||||||
int tipOffsetY = 18;
|
|
||||||
ULONGLONG tipHoverTick = 0; // 开始悬停的时间戳
|
|
||||||
int lastMouseX = 0; // 最新鼠标位置(用于定位)
|
|
||||||
int lastMouseY = 0;
|
|
||||||
|
|
||||||
std::string tipTextClick; //NORMAL 模式下用
|
|
||||||
std::string tipTextOn; // click==true 时用
|
|
||||||
std::string tipTextOff; // click==false 时用
|
|
||||||
Label tipLabel; // 直接复用Label作为提示
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StellarX::ControlText textStyle; // 按钮文字样式
|
StellarX::ControlText textStyle; // 按钮文字样式
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
//默认按钮颜色
|
//默认按钮颜色
|
||||||
Button(int x, int y, int width, int height, const std::string text,
|
Button(int x, int y, int width, int height, const std::string text,
|
||||||
StellarX::ButtonMode mode = StellarX::ButtonMode::NORMAL, StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
|
StellarX::ButtonMode mode = StellarX::ButtonMode::NORMAL, StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
|
||||||
//自定义按钮未被点击和被点击颜色
|
//自定义按钮未被点击和被点击颜色
|
||||||
Button(int x, int y, int width, int height, const std::string text,
|
Button(int x, int y, int width, int height, const std::string text,
|
||||||
COLORREF ct, COLORREF cf, StellarX::ButtonMode mode = StellarX::ButtonMode::NORMAL,
|
COLORREF ct, COLORREF cf, StellarX::ButtonMode mode = StellarX::ButtonMode::NORMAL,
|
||||||
StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
|
StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
|
||||||
//自定义按钮颜色和悬停颜色
|
//自定义按钮颜色和悬停颜色
|
||||||
Button(int x, int y, int width, int height, const std::string text,
|
Button(int x, int y, int width, int height, const std::string text,
|
||||||
COLORREF ct, COLORREF cf,COLORREF ch,
|
COLORREF ct, COLORREF cf, COLORREF ch,
|
||||||
StellarX::ButtonMode mode = StellarX::ButtonMode::NORMAL, StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
|
StellarX::ButtonMode mode = StellarX::ButtonMode::NORMAL, StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
|
||||||
//析构函数 释放图形指针内存
|
//析构函数 释放图形指针内存
|
||||||
~Button();
|
~Button();
|
||||||
|
|
||||||
//绘制按钮
|
//绘制按钮
|
||||||
void draw() override;
|
void draw() override;
|
||||||
//按钮事件处理
|
//按钮事件处理
|
||||||
bool handleEvent(const ExMessage& msg) override;
|
bool handleEvent(const ExMessage& msg) override;
|
||||||
|
|
||||||
//设置回调函数
|
//设置回调函数
|
||||||
void setOnClickListener(const std::function<void()>&& callback);
|
void setOnClickListener(const std::function<void()>&& callback);
|
||||||
//设置TOGGLE模式下被点击的回调函数
|
//设置TOGGLE模式下被点击的回调函数
|
||||||
void setOnToggleOnListener(const std::function<void()>&& callback);
|
void setOnToggleOnListener(const std::function<void()>&& callback);
|
||||||
//设置TOGGLE模式下取消点击的回调函数
|
//设置TOGGLE模式下取消点击的回调函数
|
||||||
void setOnToggleOffListener(const std::function<void()>&& callback);
|
void setOnToggleOffListener(const std::function<void()>&& callback);
|
||||||
//设置按钮模式
|
//设置按钮模式
|
||||||
void setbuttonMode(StellarX::ButtonMode mode);
|
void setbuttonMode(StellarX::ButtonMode mode);
|
||||||
//设置圆角矩形椭圆宽度
|
//设置圆角矩形椭圆宽度
|
||||||
void setROUND_RECTANGLEwidth(int width);
|
void setROUND_RECTANGLEwidth(int width);
|
||||||
//设置圆角矩形椭圆高度
|
//设置圆角矩形椭圆高度
|
||||||
void setROUND_RECTANGLEheight(int height);
|
void setROUND_RECTANGLEheight(int height);
|
||||||
//设置按钮填充模式
|
//设置按钮填充模式
|
||||||
void setFillMode(StellarX::FillMode mode);
|
void setFillMode(StellarX::FillMode mode);
|
||||||
//设置按钮填充图案
|
//设置按钮填充图案
|
||||||
void setFillIma(StellarX::FillStyle ima);
|
void setFillIma(StellarX::FillStyle ima);
|
||||||
//设置按钮填充图像
|
//设置按钮填充图像
|
||||||
void setFillIma(std::string imaName);
|
void setFillIma(std::string imaName);
|
||||||
//设置按钮边框颜色
|
//设置按钮边框颜色
|
||||||
void setButtonBorder(COLORREF Border);
|
void setButtonBorder(COLORREF Border);
|
||||||
//设置按钮未被点击颜色
|
//设置按钮未被点击颜色
|
||||||
void setButtonFalseColor(COLORREF color);
|
void setButtonFalseColor(COLORREF color);
|
||||||
//设置按钮文本
|
//设置按钮文本
|
||||||
void setButtonText(const char* text);
|
void setButtonText(const char* text);
|
||||||
void setButtonText(std::string text);
|
void setButtonText(std::string text);
|
||||||
//设置按钮形状
|
//设置按钮形状
|
||||||
void setButtonShape(StellarX::ControlShape shape);
|
void setButtonShape(StellarX::ControlShape shape);
|
||||||
//设置按钮点击状态
|
//设置按钮点击状态
|
||||||
void setButtonClick(BOOL click);
|
void setButtonClick(BOOL click);
|
||||||
|
|
||||||
//判断按钮是否被点击
|
//判断按钮是否被点击
|
||||||
bool isClicked() const;
|
bool isClicked() const;
|
||||||
|
|
||||||
//获取按钮文字
|
//获取按钮文字
|
||||||
std::string getButtonText() const;
|
std::string getButtonText() const;
|
||||||
const char* getButtonText_c() const;
|
const char* getButtonText_c() const;
|
||||||
//获取按钮模式
|
//获取按钮模式
|
||||||
StellarX::ButtonMode getButtonMode() const;
|
StellarX::ButtonMode getButtonMode() const;
|
||||||
//获取按钮形状
|
//获取按钮形状
|
||||||
StellarX::ControlShape getButtonShape() const;
|
StellarX::ControlShape getButtonShape() const;
|
||||||
//获取按钮填充模式
|
//获取按钮填充模式
|
||||||
StellarX::FillMode getFillMode() const;
|
StellarX::FillMode getFillMode() const;
|
||||||
//获取按钮填充图案
|
//获取按钮填充图案
|
||||||
StellarX::FillStyle getFillIma() const;
|
StellarX::FillStyle getFillIma() const;
|
||||||
//获取按钮填充图像
|
//获取按钮填充图像
|
||||||
IMAGE* getFillImaImage() const;
|
IMAGE* getFillImaImage() const;
|
||||||
//获取按钮边框颜色
|
//获取按钮边框颜色
|
||||||
COLORREF getButtonBorder() const;
|
COLORREF getButtonBorder() const;
|
||||||
//获取按钮文字颜色
|
//获取按钮文字颜色
|
||||||
COLORREF getButtonTextColor() const;
|
COLORREF getButtonTextColor() const;
|
||||||
//获取按钮文字样式
|
//获取按钮文字样式
|
||||||
StellarX::ControlText getButtonTextStyle() const;
|
StellarX::ControlText getButtonTextStyle() const;
|
||||||
//获取按钮宽度
|
//获取按钮宽度
|
||||||
int getButtonWidth() const;
|
int getButtonWidth() const;
|
||||||
//获取按钮高度
|
//获取按钮高度
|
||||||
int getButtonHeight() const;
|
int getButtonHeight() const;
|
||||||
public:
|
public:
|
||||||
// === Tooltip API===
|
// === Tooltip API===
|
||||||
//设置是否启用提示框
|
//设置是否启用提示框
|
||||||
void enableTooltip(bool on) { tipEnabled = on; if (!on) tipVisible = false; }
|
void enableTooltip(bool on) { tipEnabled = on; if (!on) tipVisible = false; }
|
||||||
//设置提示框延时
|
//设置提示框延时
|
||||||
void setTooltipDelay(int ms) { tipDelayMs = (ms < 0 ? 0 : ms); }
|
void setTooltipDelay(int ms) { tipDelayMs = (ms < 0 ? 0 : ms); }
|
||||||
//设置提示框是否跟随鼠标
|
//设置提示框是否跟随鼠标
|
||||||
void setTooltipFollowCursor(bool on) { tipFollowCursor = on; }
|
void setTooltipFollowCursor(bool on) { tipFollowCursor = on; }
|
||||||
//设置提示框位置偏移
|
//设置提示框位置偏移
|
||||||
void setTooltipOffset(int dx, int dy) { tipOffsetX = dx; tipOffsetY = dy; }
|
void setTooltipOffset(int dx, int dy) { tipOffsetX = dx; tipOffsetY = dy; }
|
||||||
//设置提示框样式
|
//设置提示框样式
|
||||||
void setTooltipStyle(COLORREF text, COLORREF bk, bool transparent);
|
void setTooltipStyle(COLORREF text, COLORREF bk, bool transparent);
|
||||||
//设置提示框文本
|
//设置提示框文本
|
||||||
void setTooltipText(const std::string& s){ tipTextClick = s; tipUserOverride = true; }
|
void setTooltipText(const std::string& s) { tipTextClick = s; tipUserOverride = true; }
|
||||||
void setTooltipTextsForToggle(const std::string& onText, const std::string& offText);
|
void setTooltipTextsForToggle(const std::string& onText, const std::string& offText);
|
||||||
private:
|
private:
|
||||||
//初始化按钮
|
//初始化按钮
|
||||||
void initButton(const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape, COLORREF ct, COLORREF cf, COLORREF ch);
|
void initButton(const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape, COLORREF ct, COLORREF cf, COLORREF ch);
|
||||||
//判断鼠标是否在圆形按钮内
|
//判断鼠标是否在圆形按钮内
|
||||||
bool isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius);
|
bool isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius);
|
||||||
//判断鼠标是否在椭圆按钮内
|
//判断鼠标是否在椭圆按钮内
|
||||||
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();
|
||||||
// 根据当前 click 状态选择文案
|
// 根据当前 click 状态选择文案
|
||||||
void refreshTooltipTextForState();
|
void refreshTooltipTextForState();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -23,54 +23,51 @@
|
|||||||
class Canvas : public Control
|
class Canvas : public Control
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
std::vector<std::unique_ptr<Control>> controls;
|
std::vector<std::unique_ptr<Control>> controls;
|
||||||
|
|
||||||
StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE; //容器形状
|
StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE; //容器形状
|
||||||
StellarX::FillMode canvasFillMode = StellarX::FillMode::Solid; //容器填充模式
|
StellarX::FillMode canvasFillMode = StellarX::FillMode::Solid; //容器填充模式
|
||||||
StellarX::LineStyle canvasLineStyle = StellarX::LineStyle::Solid; //线型
|
StellarX::LineStyle canvasLineStyle = StellarX::LineStyle::Solid; //线型
|
||||||
int canvaslinewidth = 1; //线宽
|
int canvaslinewidth = 1; //线宽
|
||||||
|
|
||||||
COLORREF canvasBorderClor = RGB(0, 0, 0); //边框颜色
|
COLORREF canvasBorderClor = RGB(0, 0, 0); //边框颜色
|
||||||
COLORREF canvasBkClor = RGB(255,255,255); //背景颜色
|
COLORREF canvasBkClor = RGB(255, 255, 255); //背景颜色
|
||||||
|
|
||||||
|
// 清除所有子控件
|
||||||
// 清除所有子控件
|
|
||||||
void clearAllControls();
|
void clearAllControls();
|
||||||
public:
|
public:
|
||||||
Canvas();
|
Canvas();
|
||||||
Canvas(int x, int y, int width, int height);
|
Canvas(int x, int y, int width, int height);
|
||||||
~Canvas() {}
|
~Canvas() {}
|
||||||
|
|
||||||
void setX(int x)override;
|
void setX(int x)override;
|
||||||
void setY(int y)override;
|
void setY(int y)override;
|
||||||
|
|
||||||
//绘制容器及其子控件
|
//绘制容器及其子控件
|
||||||
void draw() override;
|
void draw() override;
|
||||||
bool handleEvent(const ExMessage& msg) override;
|
bool handleEvent(const ExMessage& msg) override;
|
||||||
//添加控件
|
//添加控件
|
||||||
void addControl(std::unique_ptr<Control> control);
|
void addControl(std::unique_ptr<Control> control);
|
||||||
//设置容器样式
|
//设置容器样式
|
||||||
void setShape(StellarX::ControlShape shape);
|
void setShape(StellarX::ControlShape shape);
|
||||||
//设置容器填充模式
|
//设置容器填充模式
|
||||||
void setCanvasfillMode(StellarX::FillMode mode);
|
void setCanvasfillMode(StellarX::FillMode mode);
|
||||||
//设置容器边框颜色
|
//设置容器边框颜色
|
||||||
void setBorderColor(COLORREF color);
|
void setBorderColor(COLORREF color);
|
||||||
//设置填充颜色
|
//设置填充颜色
|
||||||
void setCanvasBkColor(COLORREF color);
|
void setCanvasBkColor(COLORREF color);
|
||||||
//设置线形
|
//设置线形
|
||||||
void setCanvasLineStyle(StellarX::LineStyle style);
|
void setCanvasLineStyle(StellarX::LineStyle style);
|
||||||
//设置线段宽度
|
//设置线段宽度
|
||||||
void setLinewidth(int width);
|
void setLinewidth(int width);
|
||||||
//设置不可见后传递给子控件重写
|
//设置不可见后传递给子控件重写
|
||||||
void setIsVisible(bool visible) override;
|
void setIsVisible(bool visible) override;
|
||||||
void setDirty(bool dirty) override;
|
void setDirty(bool dirty) override;
|
||||||
void onWindowResize() override;
|
void onWindowResize() override;
|
||||||
void requestRepaint(Control* parent)override;
|
void requestRepaint(Control* parent)override;
|
||||||
//获取子控件列表
|
//获取子控件列表
|
||||||
std::vector<std::unique_ptr<Control>>& getControls() { return controls; }
|
std::vector<std::unique_ptr<Control>>& getControls() { return controls; }
|
||||||
private:
|
private:
|
||||||
//用来检查对话框是否模态,此控件不做实现
|
//用来检查对话框是否模态,此控件不做实现
|
||||||
bool model() const override { return false; };
|
bool model() const override { return false; };
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -35,118 +35,119 @@
|
|||||||
class Control
|
class Control
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
std::string id; // 控件ID
|
std::string id; // 控件ID
|
||||||
int localx,x, localy,y; // 左上角坐标
|
int localx, x, localy, y; // 左上角坐标
|
||||||
int localWidth,width, localHeight,height; // 控件尺寸
|
int localWidth, width, localHeight, height; // 控件尺寸
|
||||||
Control* parent = nullptr; // 父控件
|
Control* parent = nullptr; // 父控件
|
||||||
bool dirty = true; // 是否重绘
|
bool dirty = true; // 是否重绘
|
||||||
bool show = true; // 是否显示
|
bool show = true; // 是否显示
|
||||||
|
|
||||||
/* == 布局模式 == */
|
/* == 布局模式 == */
|
||||||
StellarX::LayoutMode layoutMode = StellarX::LayoutMode::Fixed; // 布局模式
|
StellarX::LayoutMode layoutMode = StellarX::LayoutMode::Fixed; // 布局模式
|
||||||
StellarX::Anchor anchor_1 = StellarX::Anchor::Top; // 锚点
|
StellarX::Anchor anchor_1 = StellarX::Anchor::Top; // 锚点
|
||||||
StellarX::Anchor anchor_2 = StellarX::Anchor::Right; // 锚点
|
StellarX::Anchor anchor_2 = StellarX::Anchor::Right; // 锚点
|
||||||
|
|
||||||
/* == 背景快照 == */
|
/* == 背景快照 == */
|
||||||
IMAGE* saveBkImage = nullptr;
|
IMAGE* saveBkImage = nullptr;
|
||||||
int saveBkX = 0, saveBkY = 0; // 快照保存起始坐标
|
int saveBkX = 0, saveBkY = 0; // 快照保存起始坐标
|
||||||
int saveWidth = 0, saveHeight = 0; // 快照保存尺寸
|
int saveWidth = 0, saveHeight = 0; // 快照保存尺寸
|
||||||
bool hasSnap = false; // 当前是否持有有效快照
|
bool hasSnap = false; // 当前是否持有有效快照
|
||||||
|
|
||||||
StellarX::RouRectangle rouRectangleSize; // 圆角矩形椭圆宽度和高度
|
StellarX::RouRectangle rouRectangleSize; // 圆角矩形椭圆宽度和高度
|
||||||
|
|
||||||
LOGFONT* currentFont = new LOGFONT(); // 保存当前字体样式和颜色
|
LOGFONT* currentFont = new LOGFONT(); // 保存当前字体样式和颜色
|
||||||
COLORREF* currentColor = new COLORREF();
|
COLORREF* currentColor = new COLORREF();
|
||||||
COLORREF* currentBkColor = new COLORREF(); // 保存当前填充色
|
COLORREF* currentBkColor = new COLORREF(); // 保存当前填充色
|
||||||
COLORREF* currentBorderColor = new COLORREF(); // 边框颜色
|
COLORREF* currentBorderColor = new COLORREF(); // 边框颜色
|
||||||
LINESTYLE* currentLineStyle = new LINESTYLE(); // 保存当前线型
|
LINESTYLE* currentLineStyle = new LINESTYLE(); // 保存当前线型
|
||||||
|
|
||||||
Control(const Control&) = delete;
|
Control(const Control&) = delete;
|
||||||
Control& operator=(const Control&) = delete;
|
Control& operator=(const Control&) = delete;
|
||||||
Control(Control&&) = delete;
|
Control(Control&&) = delete;
|
||||||
Control& operator=(Control&&) = delete;
|
Control& operator=(Control&&) = delete;
|
||||||
|
|
||||||
Control() : localx(0),x(0), localy(0),y(0), localWidth(100),width(100),height(100), localHeight(100) {}
|
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)
|
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:
|
public:
|
||||||
|
|
||||||
virtual ~Control()
|
virtual ~Control()
|
||||||
{
|
{
|
||||||
delete currentFont;
|
delete currentFont;
|
||||||
delete currentColor;
|
delete currentColor;
|
||||||
delete currentBkColor;
|
delete currentBkColor;
|
||||||
delete currentBorderColor;
|
delete currentBorderColor;
|
||||||
delete currentLineStyle;
|
delete currentLineStyle;
|
||||||
|
|
||||||
currentFont = nullptr;
|
currentFont = nullptr;
|
||||||
currentColor = nullptr;
|
currentColor = nullptr;
|
||||||
currentBkColor = nullptr;
|
currentBkColor = nullptr;
|
||||||
currentBorderColor = nullptr;
|
currentBorderColor = nullptr;
|
||||||
currentLineStyle = nullptr;
|
currentLineStyle = nullptr;
|
||||||
discardBackground();
|
discardBackground();
|
||||||
}
|
}
|
||||||
protected:
|
protected:
|
||||||
//向上请求重绘
|
//向上请求重绘
|
||||||
virtual void requestRepaint(Control* parent);
|
virtual void requestRepaint(Control* parent);
|
||||||
//根控件/无父时触发重绘
|
//根控件/无父时触发重绘
|
||||||
virtual void onRequestRepaintAsRoot();
|
virtual void onRequestRepaintAsRoot();
|
||||||
protected:
|
protected:
|
||||||
//保存背景快照
|
//保存背景快照
|
||||||
virtual void saveBackground(int x, int y, int w, int h);
|
virtual void saveBackground(int x, int y, int w, int h);
|
||||||
// putimage 回屏
|
// putimage 回屏
|
||||||
virtual void restBackground();
|
virtual void restBackground();
|
||||||
// 释放快照(窗口重绘/尺寸变化后必须作废)
|
// 释放快照(窗口重绘/尺寸变化后必须作废)
|
||||||
void discardBackground();
|
void discardBackground();
|
||||||
public:
|
public:
|
||||||
//释放快照重新保存,在尺寸变化时更新背景快照避免尺寸变化导致显示错位
|
//释放快照重新保存,在尺寸变化时更新背景快照避免尺寸变化导致显示错位
|
||||||
void updateBackground();
|
void updateBackground();
|
||||||
//窗口变化丢快照
|
//窗口变化丢快照
|
||||||
virtual void onWindowResize();
|
virtual void onWindowResize();
|
||||||
// 获取位置和尺寸
|
// 获取位置和尺寸
|
||||||
int getX() const { return x; }
|
int getX() const { return x; }
|
||||||
int getY() const { return y; }
|
int getY() const { return y; }
|
||||||
int getWidth() const { return width; }
|
int getWidth() const { return width; }
|
||||||
int getHeight() const { return height; }
|
int getHeight() const { return height; }
|
||||||
int getRight() const { return x + width; }
|
int getRight() const { return x + width; }
|
||||||
int getBottom() const { return y + height; }
|
int getBottom() const { return y + height; }
|
||||||
|
|
||||||
int getLocalX() const { return localx; }
|
int getLocalX() const { return localx; }
|
||||||
int getLocalY() const { return localy; }
|
int getLocalY() const { return localy; }
|
||||||
int getLocalWidth() const { return localWidth; }
|
int getLocalWidth() const { return localWidth; }
|
||||||
int getLocalHeight() const { return localHeight; }
|
int getLocalHeight() const { return localHeight; }
|
||||||
int getLocalRight() const { return localx + localWidth; }
|
int getLocalRight() const { return localx + localWidth; }
|
||||||
int getLocalBottom() const { return localy + localHeight; }
|
int getLocalBottom() const { return localy + localHeight; }
|
||||||
|
|
||||||
virtual void setX(int x) { this->x = x; dirty = true; }
|
virtual void setX(int x) { this->x = x; dirty = true; }
|
||||||
virtual void setY(int y) { this->y = y; dirty = true; }
|
virtual void setY(int y) { this->y = y; dirty = true; }
|
||||||
virtual void setWidth(int width) { this->width = width; dirty = true; }
|
virtual void setWidth(int width) { this->width = width; dirty = true; }
|
||||||
virtual void setHeight(int height) { this->height = height; dirty = true; }
|
virtual void setHeight(int height) { this->height = height; dirty = true; }
|
||||||
public:
|
public:
|
||||||
|
|
||||||
virtual void draw() = 0;
|
virtual void draw() = 0;
|
||||||
virtual bool handleEvent(const ExMessage& msg) = 0;//返回true代表事件已消费
|
virtual bool handleEvent(const ExMessage& msg) = 0;//返回true代表事件已消费
|
||||||
//设置是否显示
|
//设置是否显示
|
||||||
virtual void setIsVisible(bool show);
|
virtual void setIsVisible(bool show);
|
||||||
//设置父容器指针
|
//设置父容器指针
|
||||||
void setParent(Control* parent) { this->parent = parent; }
|
void setParent(Control* parent) { this->parent = parent; }
|
||||||
//设置是否重绘
|
//设置是否重绘
|
||||||
virtual void setDirty(bool dirty) { this->dirty = dirty; }
|
virtual void setDirty(bool dirty) { this->dirty = dirty; }
|
||||||
//检查控件是否可见
|
//检查控件是否可见
|
||||||
bool IsVisible() const { return show; };
|
bool IsVisible() const { return show; };
|
||||||
//获取控件id
|
//获取控件id
|
||||||
std::string getId() const { return id; }
|
std::string getId() const { return id; }
|
||||||
//检查是否为脏
|
//检查是否为脏
|
||||||
bool isDirty() { return dirty; }
|
bool isDirty() { return dirty; }
|
||||||
//用来检查对话框是否模态,其他控件不用实现
|
//用来检查对话框是否模态,其他控件不用实现
|
||||||
virtual bool model()const = 0;
|
virtual bool model()const = 0;
|
||||||
//布局
|
//布局
|
||||||
void setLayoutMode(StellarX::LayoutMode layoutMode_);
|
void setLayoutMode(StellarX::LayoutMode layoutMode_);
|
||||||
void setAnchor(StellarX::Anchor anchor_1, StellarX::Anchor anchor_2);
|
void setAnchor(StellarX::Anchor anchor_1, StellarX::Anchor anchor_2);
|
||||||
StellarX::Anchor getAnchor_1() const;
|
StellarX::Anchor getAnchor_1() const;
|
||||||
StellarX::Anchor getAnchor_2() const;
|
StellarX::Anchor getAnchor_2() const;
|
||||||
StellarX::LayoutMode getLayoutMode() const;
|
StellarX::LayoutMode getLayoutMode() const;
|
||||||
protected:
|
protected:
|
||||||
void saveStyle();
|
void saveStyle();
|
||||||
void restoreStyle();
|
void restoreStyle();
|
||||||
};
|
};
|
||||||
@@ -27,363 +27,364 @@
|
|||||||
*/
|
*/
|
||||||
namespace StellarX
|
namespace StellarX
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @枚举类名称: FillStyle
|
* @枚举类名称: FillStyle
|
||||||
* @功能描述: 用来定义控件填充图案的枚举类
|
* @功能描述: 用来定义控件填充图案的枚举类
|
||||||
*
|
*
|
||||||
* @详细说明:
|
* @详细说明:
|
||||||
* 根据此枚举类可以自定义控件填充图案
|
* 根据此枚举类可以自定义控件填充图案
|
||||||
* 可以在控件初始化时设置填充图案
|
* 可以在控件初始化时设置填充图案
|
||||||
* 根据具体情况选择不同的填充图案
|
* 根据具体情况选择不同的填充图案
|
||||||
* 默认填充图案为水平线
|
* 默认填充图案为水平线
|
||||||
*
|
*
|
||||||
* @取值说明:
|
* @取值说明:
|
||||||
* Horizontal - 水平线
|
* Horizontal - 水平线
|
||||||
* Vertical - 垂直线
|
* Vertical - 垂直线
|
||||||
* FDiagonal - 反斜线
|
* FDiagonal - 反斜线
|
||||||
* BDiagonal - 正斜线
|
* BDiagonal - 正斜线
|
||||||
* Cross - 十字
|
* Cross - 十字
|
||||||
* DiagCross - 网格
|
* DiagCross - 网格
|
||||||
*
|
*
|
||||||
* @使用示例:
|
* @使用示例:
|
||||||
* FillStyle var = FillStyle::Horizontal;
|
* FillStyle var = FillStyle::Horizontal;
|
||||||
*
|
*
|
||||||
* @备注:
|
* @备注:
|
||||||
* 此枚举类仅支持图案填充模式
|
* 此枚举类仅支持图案填充模式
|
||||||
*/
|
*/
|
||||||
enum class FillStyle
|
enum class FillStyle
|
||||||
{
|
{
|
||||||
Horizontal = HS_HORIZONTAL, // 水平线
|
Horizontal = HS_HORIZONTAL, // 水平线
|
||||||
Vertical = HS_VERTICAL, // 垂直线
|
Vertical = HS_VERTICAL, // 垂直线
|
||||||
FDiagonal = HS_FDIAGONAL, // 反斜线
|
FDiagonal = HS_FDIAGONAL, // 反斜线
|
||||||
BDiagonal = HS_BDIAGONAL, // 正斜线
|
BDiagonal = HS_BDIAGONAL, // 正斜线
|
||||||
Cross = HS_CROSS, // 十字
|
Cross = HS_CROSS, // 十字
|
||||||
DiagCross = HS_DIAGCROSS // 网格
|
DiagCross = HS_DIAGCROSS // 网格
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @枚举类名称: FillMode
|
* @枚举类名称: FillMode
|
||||||
* @功能描述: 用来定义控件填充模式的枚举类
|
* @功能描述: 用来定义控件填充模式的枚举类
|
||||||
*
|
*
|
||||||
* @详细说明:
|
* @详细说明:
|
||||||
* 根据此枚举类可以自定义控件填充模式
|
* 根据此枚举类可以自定义控件填充模式
|
||||||
* 可以在控件初始化时设置填充模式
|
* 可以在控件初始化时设置填充模式
|
||||||
* 根据具体情况选择不同的填充模式
|
* 根据具体情况选择不同的填充模式
|
||||||
* 默认填充模式为固实填充
|
* 默认填充模式为固实填充
|
||||||
*
|
*
|
||||||
* @取值说明:
|
* @取值说明:
|
||||||
* Solid - 固实填充
|
* Solid - 固实填充
|
||||||
* Null - 不填充
|
* Null - 不填充
|
||||||
* Hatched - 图案填充
|
* Hatched - 图案填充
|
||||||
* Pattern - 自定义图案填充
|
* Pattern - 自定义图案填充
|
||||||
* DibPattern - 自定义图像填充
|
* DibPattern - 自定义图像填充
|
||||||
*
|
*
|
||||||
* @使用示例:
|
* @使用示例:
|
||||||
* FillMode var = FillMode::Solid;
|
* FillMode var = FillMode::Solid;
|
||||||
*/
|
*/
|
||||||
enum class FillMode
|
enum class FillMode
|
||||||
{
|
{
|
||||||
Solid = BS_SOLID, //固实填充
|
Solid = BS_SOLID, //固实填充
|
||||||
Null = BS_NULL, // 不填充
|
Null = BS_NULL, // 不填充
|
||||||
Hatched = BS_HATCHED, // 图案填充
|
Hatched = BS_HATCHED, // 图案填充
|
||||||
Pattern = BS_PATTERN, // 自定义图案填充
|
Pattern = BS_PATTERN, // 自定义图案填充
|
||||||
DibPattern = BS_DIBPATTERN // 自定义图像填充
|
DibPattern = BS_DIBPATTERN // 自定义图像填充
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @枚举类名称: LineStyle
|
* @枚举类名称: LineStyle
|
||||||
* @功能描述: 此枚举类用来定义控件边框线型
|
* @功能描述: 此枚举类用来定义控件边框线型
|
||||||
*
|
*
|
||||||
* @详细说明:
|
* @详细说明:
|
||||||
* 根据此枚举类可以自定义控件边框线型
|
* 根据此枚举类可以自定义控件边框线型
|
||||||
* 可以在控件初始化时设置边框线型
|
* 可以在控件初始化时设置边框线型
|
||||||
* 根据具体情况选择不同的线型
|
* 根据具体情况选择不同的线型
|
||||||
* 默认线型为实线
|
* 默认线型为实线
|
||||||
*
|
*
|
||||||
* @取值说明:
|
* @取值说明:
|
||||||
* Solid // 实线
|
* Solid // 实线
|
||||||
* Dash // 虚线
|
* Dash // 虚线
|
||||||
* Dot // 点线
|
* Dot // 点线
|
||||||
* DashDot // 点划线
|
* DashDot // 点划线
|
||||||
* DashDotDot // 双点划线
|
* DashDotDot // 双点划线
|
||||||
* Null // 无线
|
* Null // 无线
|
||||||
*
|
*
|
||||||
* @使用示例:
|
* @使用示例:
|
||||||
* LineStyle var = LineStyle::Solid;
|
* LineStyle var = LineStyle::Solid;
|
||||||
*/
|
*/
|
||||||
enum class LineStyle
|
enum class LineStyle
|
||||||
{
|
{
|
||||||
Solid = PS_SOLID, // 实线
|
Solid = PS_SOLID, // 实线
|
||||||
Dash = PS_DASH, // 虚线
|
Dash = PS_DASH, // 虚线
|
||||||
Dot = PS_DOT, // 点线
|
Dot = PS_DOT, // 点线
|
||||||
DashDot = PS_DASHDOT, // 点划线
|
DashDot = PS_DASHDOT, // 点划线
|
||||||
DashDotDot = PS_DASHDOTDOT, // 双点划线
|
DashDotDot = PS_DASHDOTDOT, // 双点划线
|
||||||
Null = PS_NULL // 无线
|
Null = PS_NULL // 无线
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @结构体名称: ControlText
|
* @结构体名称: ControlText
|
||||||
* @功能描述: 控件字体样式 可以自定义不同的样式
|
* @功能描述: 控件字体样式 可以自定义不同的样式
|
||||||
*
|
*
|
||||||
* @详细说明:
|
* @详细说明:
|
||||||
* 主要使用的场景为:需要修改或想自定义控件字体大小,字体样式,颜色等
|
* 主要使用的场景为:需要修改或想自定义控件字体大小,字体样式,颜色等
|
||||||
*
|
*
|
||||||
* @成员说明:
|
* @成员说明:
|
||||||
* int nHeight = 0; - 字体高度
|
* int nHeight = 0; - 字体高度
|
||||||
* int nWidth = 0; - 字体宽度 如果为0则自适应
|
* int nWidth = 0; - 字体宽度 如果为0则自适应
|
||||||
* LPCTSTR lpszFace = "宋体"; - 字体名称
|
* LPCTSTR lpszFace = "宋体"; - 字体名称
|
||||||
* COLORREF color = RGB(0, 0, 0); - 字体颜色
|
* COLORREF color = RGB(0, 0, 0); - 字体颜色
|
||||||
* int nEscapement = 0; - 字符串旋转角度
|
* int nEscapement = 0; - 字符串旋转角度
|
||||||
* int nOrientation = 0; - 字符旋转角度
|
* int nOrientation = 0; - 字符旋转角度
|
||||||
* int nWeight = 0; - 字体粗细 范围0~1000 0表示默认
|
* int nWeight = 0; - 字体粗细 范围0~1000 0表示默认
|
||||||
* bool bItalic = false; - 是否斜体
|
* bool bItalic = false; - 是否斜体
|
||||||
* bool bUnderline = false; - 是否下划线
|
* bool bUnderline = false; - 是否下划线
|
||||||
* bool bStrikeOut = false; - 是否删除线
|
* bool bStrikeOut = false; - 是否删除线
|
||||||
*/
|
*/
|
||||||
struct ControlText
|
struct ControlText
|
||||||
{
|
{
|
||||||
int nHeight = 0; //- 字体高度
|
int nHeight = 0; //- 字体高度
|
||||||
int nWidth = 0; //- 字体宽度 如果为0则自适应
|
int nWidth = 0; //- 字体宽度 如果为0则自适应
|
||||||
LPCTSTR lpszFace = "微软雅黑"; //- 字体名称
|
LPCTSTR lpszFace = "微软雅黑"; //- 字体名称
|
||||||
COLORREF color = RGB(0, 0, 0); //- 字体颜色
|
COLORREF color = RGB(0, 0, 0); //- 字体颜色
|
||||||
int nEscapement = 0; //- 字符串旋转角度
|
int nEscapement = 0; //- 字符串旋转角度
|
||||||
int nOrientation = 0; //- 字符旋转角度
|
int nOrientation = 0; //- 字符旋转角度
|
||||||
int nWeight = 0; //- 字体粗细 范围0~1000 0表示默认
|
int nWeight = 0; //- 字体粗细 范围0~1000 0表示默认
|
||||||
bool bItalic = false; //- 是否斜体
|
bool bItalic = false; //- 是否斜体
|
||||||
bool bUnderline = false; //- 是否下划线
|
bool bUnderline = false; //- 是否下划线
|
||||||
bool bStrikeOut = false; //- 是否删除线
|
bool bStrikeOut = false; //- 是否删除线
|
||||||
|
|
||||||
bool operator!=(const ControlText& text);
|
bool operator!=(const ControlText& text);
|
||||||
ControlText& operator=(const ControlText& text);
|
ControlText& operator=(const ControlText& text);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @枚举名称: ControlShape
|
* @枚举名称: ControlShape
|
||||||
* @功能描述: 枚举控件的不同几何样式
|
* @功能描述: 枚举控件的不同几何样式
|
||||||
*
|
*
|
||||||
* @详细说明:
|
* @详细说明:
|
||||||
* 定义了四种(有无边框算一种)不同的几何样式,可以根据具体需求
|
* 定义了四种(有无边框算一种)不同的几何样式,可以根据具体需求
|
||||||
* 自定义控件的形状。
|
* 自定义控件的形状。
|
||||||
*
|
*
|
||||||
* @取值说明:
|
* @取值说明:
|
||||||
* RECTANGLE = 1, //有边框矩形
|
* RECTANGLE = 1, //有边框矩形
|
||||||
* B_RECTANGLE, //无边框矩形
|
* B_RECTANGLE, //无边框矩形
|
||||||
* ROUND_RECTANGLE, //有边框圆角矩形
|
* ROUND_RECTANGLE, //有边框圆角矩形
|
||||||
* B_ROUND_RECTANGLE, //无边框圆角矩形
|
* B_ROUND_RECTANGLE, //无边框圆角矩形
|
||||||
* CIRCLE, //有边框圆形
|
* CIRCLE, //有边框圆形
|
||||||
* B_CIRCLE, //无边框圆形
|
* B_CIRCLE, //无边框圆形
|
||||||
* ELLIPSE, //有边框椭圆
|
* ELLIPSE, //有边框椭圆
|
||||||
* B_ELLIPSE //无边框椭圆
|
* B_ELLIPSE //无边框椭圆
|
||||||
*
|
*
|
||||||
* @使用示例:
|
* @使用示例:
|
||||||
* ControlShape shape = ControlShape::ELLIPSE;
|
* ControlShape shape = ControlShape::ELLIPSE;
|
||||||
*
|
*
|
||||||
* @备注:
|
* @备注:
|
||||||
* 按钮类支持所有形状,部分控件只支持部分形状,具体请参考控件类。
|
* 按钮类支持所有形状,部分控件只支持部分形状,具体请参考控件类。
|
||||||
*/
|
*/
|
||||||
enum class ControlShape
|
enum class ControlShape
|
||||||
{
|
{
|
||||||
RECTANGLE = 1, //有边框矩形
|
RECTANGLE = 1, //有边框矩形
|
||||||
B_RECTANGLE, //无边框矩形
|
B_RECTANGLE, //无边框矩形
|
||||||
ROUND_RECTANGLE, //有边框圆角矩形
|
ROUND_RECTANGLE, //有边框圆角矩形
|
||||||
B_ROUND_RECTANGLE, //无边框圆角矩形
|
B_ROUND_RECTANGLE, //无边框圆角矩形
|
||||||
CIRCLE, //有边框圆形
|
CIRCLE, //有边框圆形
|
||||||
B_CIRCLE, //无边框圆形
|
B_CIRCLE, //无边框圆形
|
||||||
ELLIPSE, //有边框椭圆
|
ELLIPSE, //有边框椭圆
|
||||||
B_ELLIPSE //无边框椭圆
|
B_ELLIPSE //无边框椭圆
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @枚举类名称: TextBoxmode
|
* @枚举类名称: TextBoxmode
|
||||||
* @功能描述: 定义了文本框的两种模式
|
* @功能描述: 定义了文本框的两种模式
|
||||||
*
|
*
|
||||||
* @详细说明:
|
* @详细说明:
|
||||||
* 需要限制文本框是否接受用户输入时使用
|
* 需要限制文本框是否接受用户输入时使用
|
||||||
*
|
*
|
||||||
* @取值说明:
|
* @取值说明:
|
||||||
* INPUT_MODE, // 用户可输入模式
|
* INPUT_MODE, // 用户可输入模式
|
||||||
* READONLY_MODE // 只读模式
|
* READONLY_MODE // 只读模式
|
||||||
*/
|
*/
|
||||||
enum class TextBoxmode
|
enum class TextBoxmode
|
||||||
{
|
{
|
||||||
INPUT_MODE, // 用户可输入模式
|
INPUT_MODE, // 用户可输入模式
|
||||||
READONLY_MODE // 只读模式
|
READONLY_MODE, // 只读模式
|
||||||
};
|
PASSWORD_MODE// 密码模式
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @枚举名称: ButtonMode
|
* @枚举名称: ButtonMode
|
||||||
* @功能描述: 定义按钮的工作模式
|
* @功能描述: 定义按钮的工作模式
|
||||||
*
|
*
|
||||||
* @详细说明:
|
* @详细说明:
|
||||||
* 根据按钮的工作模式,按钮可以有不同的行为。
|
* 根据按钮的工作模式,按钮可以有不同的行为。
|
||||||
* 用户可以在具体情况下设置按钮的工作模式。
|
* 用户可以在具体情况下设置按钮的工作模式。
|
||||||
*
|
*
|
||||||
* @取值说明:
|
* @取值说明:
|
||||||
* NORMAL = 1, - 普通模式,点击后触发回调,但不会保持状态。
|
* NORMAL = 1, - 普通模式,点击后触发回调,但不会保持状态。
|
||||||
* TOGGLE, - 切换模式,点击后会在选中和未选中之间切换,触发不同的回调函数。
|
* TOGGLE, - 切换模式,点击后会在选中和未选中之间切换,触发不同的回调函数。
|
||||||
* DISABLED - 禁用模式,按钮不可点击,显示为灰色,文本显示删除线。
|
* DISABLED - 禁用模式,按钮不可点击,显示为灰色,文本显示删除线。
|
||||||
*
|
*
|
||||||
* @使用示例:
|
* @使用示例:
|
||||||
* ButtonMode mode = ButtonMode::NORMAL;
|
* ButtonMode mode = ButtonMode::NORMAL;
|
||||||
*/
|
*/
|
||||||
enum class ButtonMode
|
enum class ButtonMode
|
||||||
{
|
{
|
||||||
NORMAL = 1, //普通模式
|
NORMAL = 1, //普通模式
|
||||||
TOGGLE, //切换模式
|
TOGGLE, //切换模式
|
||||||
DISABLED //禁用模式
|
DISABLED //禁用模式
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @结构体名称: RouRectangle
|
* @结构体名称: RouRectangle
|
||||||
* @功能描述: 定义了控件圆角矩形样式时圆角的椭圆尺寸
|
* @功能描述: 定义了控件圆角矩形样式时圆角的椭圆尺寸
|
||||||
*
|
*
|
||||||
* @详细说明:
|
* @详细说明:
|
||||||
* 需要修改控件圆角矩形样式时的圆角椭圆。
|
* 需要修改控件圆角矩形样式时的圆角椭圆。
|
||||||
*
|
*
|
||||||
* @成员说明:
|
* @成员说明:
|
||||||
* int ROUND_RECTANGLEwidth = 20; //构成圆角矩形的圆角的椭圆的宽度。
|
* int ROUND_RECTANGLEwidth = 20; //构成圆角矩形的圆角的椭圆的宽度。
|
||||||
* int ROUND_RECTANGLEheight = 20; //构成圆角矩形的圆角的椭圆的高度。
|
* int ROUND_RECTANGLEheight = 20; //构成圆角矩形的圆角的椭圆的高度。
|
||||||
*/
|
*/
|
||||||
struct RouRectangle
|
struct RouRectangle
|
||||||
{
|
{
|
||||||
int ROUND_RECTANGLEwidth = 20; //构成圆角矩形的圆角的椭圆的宽度。
|
int ROUND_RECTANGLEwidth = 20; //构成圆角矩形的圆角的椭圆的宽度。
|
||||||
int ROUND_RECTANGLEheight = 20; //构成圆角矩形的圆角的椭圆的高度。
|
int ROUND_RECTANGLEheight = 20; //构成圆角矩形的圆角的椭圆的高度。
|
||||||
};
|
};
|
||||||
|
|
||||||
// 消息框类型
|
// 消息框类型
|
||||||
enum class MessageBoxType
|
enum class MessageBoxType
|
||||||
{
|
{
|
||||||
OK, // 只有确定按钮
|
OK, // 只有确定按钮
|
||||||
OKCancel, // 确定和取消按钮
|
OKCancel, // 确定和取消按钮
|
||||||
YesNo, // 是和否按钮
|
YesNo, // 是和否按钮
|
||||||
YesNoCancel, // 是、否和取消按钮
|
YesNoCancel, // 是、否和取消按钮
|
||||||
RetryCancel, // 重试和取消按钮
|
RetryCancel, // 重试和取消按钮
|
||||||
AbortRetryIgnore, // 中止、重试和忽略按钮
|
AbortRetryIgnore, // 中止、重试和忽略按钮
|
||||||
};
|
};
|
||||||
|
|
||||||
// 消息框返回值
|
// 消息框返回值
|
||||||
enum class MessageBoxResult
|
enum class MessageBoxResult
|
||||||
{
|
{
|
||||||
OK = 1, // 确定按钮
|
OK = 1, // 确定按钮
|
||||||
Cancel = 2, // 取消按钮
|
Cancel = 2, // 取消按钮
|
||||||
Yes = 6, // 是按钮
|
Yes = 6, // 是按钮
|
||||||
No = 7, // 否按钮
|
No = 7, // 否按钮
|
||||||
Abort = 3, // 中止按钮
|
Abort = 3, // 中止按钮
|
||||||
Retry = 4, // 重试按钮
|
Retry = 4, // 重试按钮
|
||||||
Ignore = 5 // 忽略按钮
|
Ignore = 5 // 忽略按钮
|
||||||
};
|
};
|
||||||
#if 0 //布局管理器相关 —待实现—
|
#if 0 //布局管理器相关 —待实现—
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
*@枚举名称: LayoutKind
|
*@枚举名称: LayoutKind
|
||||||
* @功能描述 : 定义布局管理类型
|
* @功能描述 : 定义布局管理类型
|
||||||
*
|
*
|
||||||
*@详细说明 :
|
*@详细说明 :
|
||||||
* 根据布局管理类型,控件可以有不同的布局方式。
|
* 根据布局管理类型,控件可以有不同的布局方式。
|
||||||
* 用户可以在具体情况下设置布局管理类型。
|
* 用户可以在具体情况下设置布局管理类型。
|
||||||
*
|
*
|
||||||
* @取值说明 :
|
* @取值说明 :
|
||||||
* Absolute:不管,保持子控件自己的坐标(向后兼容)。
|
* Absolute:不管,保持子控件自己的坐标(向后兼容)。
|
||||||
* HBox: 水平方向排队;支持固定宽、权重分配、对齐、拉伸。
|
* HBox: 水平方向排队;支持固定宽、权重分配、对齐、拉伸。
|
||||||
* VBox: 竖直方向排队;同上。
|
* VBox: 竖直方向排队;同上。
|
||||||
* Grid(网格):按行列摆;支持固定/自适应/权重行列;支持跨行/跨列;单元内对齐/拉伸。
|
* Grid(网格):按行列摆;支持固定/自适应/权重行列;支持跨行/跨列;单元内对齐/拉伸。
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
// 布局类型
|
// 布局类型
|
||||||
enum class LayoutKind
|
enum class LayoutKind
|
||||||
{
|
{
|
||||||
Absolute = 1,
|
Absolute = 1,
|
||||||
HBox,
|
HBox,
|
||||||
VBox,
|
VBox,
|
||||||
Grid,
|
Grid,
|
||||||
Flow,
|
Flow,
|
||||||
Stack
|
Stack
|
||||||
};
|
};
|
||||||
|
|
||||||
// 布局参数
|
// 布局参数
|
||||||
struct LayoutParams
|
struct LayoutParams
|
||||||
{
|
{
|
||||||
// 边距左、右、上、下
|
// 边距左、右、上、下
|
||||||
int marginL = 0, marginR = 0, marginT = 0, marginB = 0;
|
int marginL = 0, marginR = 0, marginT = 0, marginB = 0;
|
||||||
// 固定尺寸(>=0 强制;-1 用控件当前尺寸)
|
// 固定尺寸(>=0 强制;-1 用控件当前尺寸)
|
||||||
int fixedW = -1, fixedH = -1;
|
int fixedW = -1, fixedH = -1;
|
||||||
// 主轴权重(HBox=宽度、VBox=高度、Grid见下)
|
// 主轴权重(HBox=宽度、VBox=高度、Grid见下)
|
||||||
float weight = 0.f;
|
float weight = 0.f;
|
||||||
// 对齐(非拉伸时生效)
|
// 对齐(非拉伸时生效)
|
||||||
enum Align { Start = 0, Center = 1, End = 2, Stretch = 3 };
|
enum Align { Start = 0, Center = 1, End = 2, Stretch = 3 };
|
||||||
int alignX = Start; // HBox: 次轴=Y;VBox: 次轴=X;Grid: 单元内
|
int alignX = Start; // HBox: 次轴=Y;VBox: 次轴=X;Grid: 单元内
|
||||||
int alignY = Start; // Grid :控制单元内垂直(HBox / VBox通常只用 alignX)
|
int alignY = Start; // Grid :控制单元内垂直(HBox / VBox通常只用 alignX)
|
||||||
// Grid 专用(可先不做)
|
// Grid 专用(可先不做)
|
||||||
int gridRow = 0, gridCol = 0, rowSpan = 1, colSpan = 1;
|
int gridRow = 0, gridCol = 0, rowSpan = 1, colSpan = 1;
|
||||||
// Flow 专用(可先不做)
|
// Flow 专用(可先不做)
|
||||||
int flowBreak = 0; // 1=强制换行
|
int flowBreak = 0; // 1=强制换行
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @枚举名称: TabPlacement
|
* @枚举名称: TabPlacement
|
||||||
* @功能描述: 定义了选项卡页签的不同位置
|
* @功能描述: 定义了选项卡页签的不同位置
|
||||||
*
|
*
|
||||||
* @详细说明:
|
* @详细说明:
|
||||||
* 根据选项卡页签的位置,选项卡页签可以有不同的布局方式。
|
* 根据选项卡页签的位置,选项卡页签可以有不同的布局方式。
|
||||||
*
|
*
|
||||||
* @成员说明:
|
* @成员说明:
|
||||||
* Top, - 选项卡页签位于顶部
|
* Top, - 选项卡页签位于顶部
|
||||||
* Bottom, - 选项卡页签位于底部
|
* Bottom, - 选项卡页签位于底部
|
||||||
* Left, - 选项卡页签位于左侧
|
* Left, - 选项卡页签位于左侧
|
||||||
* Right - 选项卡页签位于右侧
|
* Right - 选项卡页签位于右侧
|
||||||
*
|
*
|
||||||
* @使用示例:
|
* @使用示例:
|
||||||
* TabPlacement placement = TabPlacement::Top;
|
* TabPlacement placement = TabPlacement::Top;
|
||||||
*/
|
*/
|
||||||
enum class TabPlacement
|
enum class TabPlacement
|
||||||
{
|
{
|
||||||
Top,
|
Top,
|
||||||
Bottom,
|
Bottom,
|
||||||
Left,
|
Left,
|
||||||
Right
|
Right
|
||||||
};
|
};
|
||||||
/*
|
/*
|
||||||
* @枚举名称: LayoutMode
|
* @枚举名称: LayoutMode
|
||||||
* @功能描述: 定义了两种布局模式
|
* @功能描述: 定义了两种布局模式
|
||||||
*
|
*
|
||||||
* @详细说明:
|
* @详细说明:
|
||||||
* 根据不同模式,在窗口拉伸时采用不同的布局策略
|
* 根据不同模式,在窗口拉伸时采用不同的布局策略
|
||||||
*
|
*
|
||||||
* @成员说明:
|
* @成员说明:
|
||||||
* Fixed, - 固定布局
|
* Fixed, - 固定布局
|
||||||
* AnchorToEdges - 锚定布局
|
* AnchorToEdges - 锚定布局
|
||||||
*
|
*
|
||||||
* @使用示例:
|
* @使用示例:
|
||||||
* LayoutMode mode = LayoutMode::Fixed;
|
* LayoutMode mode = LayoutMode::Fixed;
|
||||||
*/
|
*/
|
||||||
enum class LayoutMode
|
enum class LayoutMode
|
||||||
{
|
{
|
||||||
Fixed,
|
Fixed,
|
||||||
AnchorToEdges
|
AnchorToEdges
|
||||||
};
|
};
|
||||||
/*
|
/*
|
||||||
* @枚举名称: Anchor
|
* @枚举名称: Anchor
|
||||||
* @功能描述: 定义了控件相对于窗口锚定的位置
|
* @功能描述: 定义了控件相对于窗口锚定的位置
|
||||||
*
|
*
|
||||||
* @详细说明:
|
* @详细说明:
|
||||||
* 根据不同的锚定位置,有不同的拉伸策略
|
* 根据不同的锚定位置,有不同的拉伸策略
|
||||||
*
|
*
|
||||||
* @成员说明:
|
* @成员说明:
|
||||||
* Top, - 锚定上边,控件上边与窗口上侧距离保持不变
|
* Top, - 锚定上边,控件上边与窗口上侧距离保持不变
|
||||||
* Bottom, - 锚定底边,控件底边与窗口底边距离保持不变
|
* Bottom, - 锚定底边,控件底边与窗口底边距离保持不变
|
||||||
* Left, - 锚定左边,控件左边与窗口左侧距离保持不变
|
* Left, - 锚定左边,控件左边与窗口左侧距离保持不变
|
||||||
* Right - 锚定右边,控件上边与窗口右侧距离保持不变
|
* Right - 锚定右边,控件上边与窗口右侧距离保持不变
|
||||||
*
|
*
|
||||||
* @使用示例:
|
* @使用示例:
|
||||||
* Anchor a = Anchor::Top;
|
* Anchor a = Anchor::Top;
|
||||||
*/
|
*/
|
||||||
enum class Anchor
|
enum class Anchor
|
||||||
{
|
{
|
||||||
NoAnchor = 0,
|
NoAnchor = 0,
|
||||||
Left = 1,
|
Left = 1,
|
||||||
Right,
|
Right,
|
||||||
Top,
|
Top,
|
||||||
Bottom
|
Bottom
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,6 @@ class Dialog : public Canvas
|
|||||||
std::string message; //提示信息
|
std::string message; //提示信息
|
||||||
std::vector<std::string> lines; //消息内容按行分割
|
std::vector<std::string> lines; //消息内容按行分割
|
||||||
|
|
||||||
|
|
||||||
bool needsInitialization = true; //是否需要初始化
|
bool needsInitialization = true; //是否需要初始化
|
||||||
bool close = false; //是否关闭
|
bool close = false; //是否关闭
|
||||||
bool modal = true; //是否模态
|
bool modal = true; //是否模态
|
||||||
@@ -57,7 +56,6 @@ class Dialog : public Canvas
|
|||||||
COLORREF buttonFalseColor = RGB(215, 215, 215); //按钮未被点击颜色
|
COLORREF buttonFalseColor = RGB(215, 215, 215); //按钮未被点击颜色
|
||||||
COLORREF buttonHoverColor = RGB(224, 224, 224); //按钮悬浮颜色
|
COLORREF buttonHoverColor = RGB(224, 224, 224); //按钮悬浮颜色
|
||||||
|
|
||||||
|
|
||||||
Button* closeButton = nullptr; //关闭按钮
|
Button* closeButton = nullptr; //关闭按钮
|
||||||
|
|
||||||
StellarX::MessageBoxResult result = StellarX::MessageBoxResult::Cancel; // 对话框结果
|
StellarX::MessageBoxResult result = StellarX::MessageBoxResult::Cancel; // 对话框结果
|
||||||
@@ -78,7 +76,6 @@ public:
|
|||||||
//获取对话框消息,用以去重
|
//获取对话框消息,用以去重
|
||||||
std::string GetText() const;
|
std::string GetText() const;
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Dialog(Window& hWnd, std::string text, std::string message = "对话框", StellarX::MessageBoxType type = StellarX::MessageBoxType::OK, bool modal = true);
|
Dialog(Window& hWnd, std::string text, std::string message = "对话框", StellarX::MessageBoxType type = StellarX::MessageBoxType::OK, bool modal = true);
|
||||||
~Dialog();
|
~Dialog();
|
||||||
@@ -109,7 +106,6 @@ public:
|
|||||||
//初始化
|
//初始化
|
||||||
void setInitialization(bool init);
|
void setInitialization(bool init);
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// 初始化按钮
|
// 初始化按钮
|
||||||
void initButtons();
|
void initButtons();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* @文件: StellarX.h
|
* @文件: StellarX.h
|
||||||
* @摘要: 星垣(StellarX) GUI框架 - 主包含头文件
|
* @摘要: 星垣(StellarX) GUI框架 - 主包含头文件
|
||||||
* @版本: v2.3.1
|
* @版本: v3.0.0
|
||||||
* @描述:
|
* @描述:
|
||||||
* 一个为Windows平台打造的轻量级、模块化C++ GUI框架。
|
* 一个为Windows平台打造的轻量级、模块化C++ GUI框架。
|
||||||
* 基于EasyX图形库,提供简洁易用的API和丰富的控件。
|
* 基于EasyX图形库,提供简洁易用的API和丰富的控件。
|
||||||
@@ -11,8 +11,8 @@
|
|||||||
*
|
*
|
||||||
* @作者: 我在人间做废物
|
* @作者: 我在人间做废物
|
||||||
* @邮箱: [3150131407@qq.com] | [ysm3150131407@gmail.com]
|
* @邮箱: [3150131407@qq.com] | [ysm3150131407@gmail.com]
|
||||||
|
* @官网:https://stellarx-gui.top/
|
||||||
* @仓库: [https://github.com/Ysm-04/StellarX]
|
* @仓库: [https://github.com/Ysm-04/StellarX]
|
||||||
* @官网:即将上线,敬请期待
|
|
||||||
*
|
*
|
||||||
* @许可证: MIT License
|
* @许可证: MIT License
|
||||||
* @版权: Copyright (c) 2025 我在人间做废物
|
* @版权: Copyright (c) 2025 我在人间做废物
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreTypes.h"
|
#include "CoreTypes.h"
|
||||||
|
#include "SxLog.h"
|
||||||
#include "Control.h"
|
#include "Control.h"
|
||||||
#include"Canvas.h"
|
#include"Canvas.h"
|
||||||
#include"Window.h"
|
#include"Window.h"
|
||||||
@@ -41,3 +42,5 @@
|
|||||||
#include"Dialog.h"
|
#include"Dialog.h"
|
||||||
#include"MessageBox.h"
|
#include"MessageBox.h"
|
||||||
#include"TabControl.h"
|
#include"TabControl.h"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
416
include/StellarX/SxLog.h
Normal file
416
include/StellarX/SxLog.h
Normal file
@@ -0,0 +1,416 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/********************************************************************************
|
||||||
|
* @文件: SxLog.h
|
||||||
|
* @摘要: StellarX 日志系统对外接口定义(控制台/文件输出 + 级别过滤 + Tag过滤 + 中英文选择)
|
||||||
|
* @描述:
|
||||||
|
* 该日志系统采用“宏 + RAII(析构提交)”的方式实现:
|
||||||
|
* - 调用端通过 SX_LOGD/SX_LOGI... 写日志
|
||||||
|
* - 宏内部先 shouldLog 短路过滤,未命中时不构造对象、不拼接字符串
|
||||||
|
* - 命中时构造 SxLogLine,使用 operator<< 拼接内容
|
||||||
|
* - 语句结束时 SxLogLine 析构,统一提交到 SxLogger::logLine 输出
|
||||||
|
*
|
||||||
|
* 输出通道(Sink)目前提供:
|
||||||
|
* - ConsoleSink: 写入 std::cout(不走 WinAPI 调试输出通道)
|
||||||
|
* - FileSink: 写入文件,支持按字节阈值滚动
|
||||||
|
*
|
||||||
|
* @特性:
|
||||||
|
* - 日志级别:Trace/Debug/Info/Warn/Error/Fatal/Off
|
||||||
|
* - Tag 过滤:None/Whitelist/Blacklist
|
||||||
|
* - 可选前缀:时间戳/级别/Tag/线程ID/源码位置
|
||||||
|
* - 中英文选择:SX_T(zh, en) / setLanguage
|
||||||
|
* - 文件滚动:rotateBytes > 0 时按阈值滚动
|
||||||
|
*
|
||||||
|
* @使用场景:
|
||||||
|
* - 排查重绘链路、脏标记传播、Tab 切换、Table 数据刷新等时序问题
|
||||||
|
* - 输出可复现日志,配合回归验证
|
||||||
|
*
|
||||||
|
* @注意:
|
||||||
|
* - SX_T 仅做“字符串选择”,不做编码转换
|
||||||
|
* - 控制台显示是否乱码由“终端 codepage/字体/环境”决定
|
||||||
|
* - 该头文件只声明接口,实现位于 SxLog.cpp
|
||||||
|
*
|
||||||
|
* @所属框架: 星垣(StellarX) GUI框架
|
||||||
|
* @作者: 我在人间做废物
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
// SxLog.h - header-only interface (implementation in SxLog.cpp)
|
||||||
|
// Pure standard library: std::cout and optional file sink.
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <ctime>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#ifndef SX_LOG_ENABLE
|
||||||
|
#define SX_LOG_ENABLE 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace StellarX
|
||||||
|
{
|
||||||
|
/* ========================= 日志级别 ========================= */
|
||||||
|
// 说明:
|
||||||
|
// - minLevel 表示最低输出级别,小于 minLevel 的日志会被 shouldLog 直接过滤
|
||||||
|
// - Off 表示全局关闭
|
||||||
|
enum class SxLogLevel : int
|
||||||
|
{
|
||||||
|
Trace = 0, // 最细粒度:高频路径追踪(谨慎开启)
|
||||||
|
Debug = 1, // 调试信息:状态变化/关键分支
|
||||||
|
Info = 2, // 业务信息:关键流程节点
|
||||||
|
Warn = 3, // 警告:非致命但异常的情况
|
||||||
|
Error = 4, // 错误:功能失败、需要关注
|
||||||
|
Fatal = 5, // 致命:通常意味着无法继续运行
|
||||||
|
Off = 6 // 关闭全部日志
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ========================= 语言选择 ========================= */
|
||||||
|
// 说明:仅用于 SX_T 选择输出哪一段文本,不做编码转换
|
||||||
|
enum class SxLogLanguage : int
|
||||||
|
{
|
||||||
|
ZhCN = 0, // 中文
|
||||||
|
EnUS = 1 // 英文
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ========================= Tag 过滤模式 ========================= */
|
||||||
|
// None : 不过滤,全部输出
|
||||||
|
// Whitelist : 只输出 tagList 中包含的 tag
|
||||||
|
// Blacklist : 输出除 tagList 以外的 tag
|
||||||
|
enum class SxTagFilterMode : int
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Whitelist = 1,
|
||||||
|
Blacklist = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ========================= 日志配置 ========================= */
|
||||||
|
// 说明:SxLogger 内部持有该配置,shouldLog 与 logLine 都依赖它
|
||||||
|
struct SxLogConfig
|
||||||
|
{
|
||||||
|
SxLogLevel minLevel = SxLogLevel::Info; // 最低输出级别
|
||||||
|
|
||||||
|
bool showTimestamp = true; // 是否输出时间戳前缀
|
||||||
|
bool showLevel = true; // 是否输出级别前缀
|
||||||
|
bool showTag = true; // 是否输出 tag 前缀
|
||||||
|
bool showThreadId = false; // 是否输出线程ID(排查并发时开启)
|
||||||
|
bool showSource = false; // 是否输出源码位置(file:line func)
|
||||||
|
bool autoFlush = true; // 每行写完是否 flush(排查问题更稳,性能略差)
|
||||||
|
|
||||||
|
SxTagFilterMode tagFilterMode = SxTagFilterMode::None; // Tag 过滤模式
|
||||||
|
std::vector<std::string> tagList; // Tag 列表(白名单/黑名单)
|
||||||
|
|
||||||
|
bool fileEnabled = false; // 文件输出是否启用(enableFile 成功才为 true)
|
||||||
|
std::string filePath; // 文件路径
|
||||||
|
bool fileAppend = true; // 是否追加写入
|
||||||
|
std::size_t rotateBytes = 0; // 滚动阈值(0 表示不滚动)
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ========================= Sink 接口 ========================= */
|
||||||
|
// 说明:
|
||||||
|
// - Sink 负责“把完整的一行日志写到某个地方”
|
||||||
|
// - SxLogger 负责过滤/格式化/分发
|
||||||
|
class ILogSink
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~ILogSink() = default;
|
||||||
|
|
||||||
|
// 返回 Sink 名称,用于调试识别(例如 "console"/"file")
|
||||||
|
virtual const char* name() const = 0;
|
||||||
|
|
||||||
|
// 写入一整行(调用方保证 line 已包含换行或按约定追加换行)
|
||||||
|
virtual void writeLine(const std::string& line) = 0;
|
||||||
|
|
||||||
|
// 刷新缓冲(可选实现)
|
||||||
|
virtual void flush() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ========================= 控制台输出 Sink ========================= */
|
||||||
|
// 作用:把日志写入指定输出流(默认用 std::cout)
|
||||||
|
class ConsoleSink : public ILogSink
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// 绑定一个输出流引用(常见用法:std::cout)
|
||||||
|
explicit ConsoleSink(std::ostream& os) : out(os) {}
|
||||||
|
|
||||||
|
const char* name() const override { return "console"; }
|
||||||
|
|
||||||
|
// 写入一行(不自动追加换行,换行由上层统一拼接)
|
||||||
|
void writeLine(const std::string& line) override { out << line; }
|
||||||
|
|
||||||
|
// 立即 flush(当 autoFlush=true 时由 SxLogger 调用)
|
||||||
|
void flush() override { out.flush(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::ostream& out; // 输出流引用(不负责生命周期)
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ========================= 文件输出 Sink ========================= */
|
||||||
|
// 作用:把日志写入文件,支持按字节阈值滚动
|
||||||
|
class FileSink : public ILogSink
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FileSink() = default;
|
||||||
|
|
||||||
|
const char* name() const override { return "file"; }
|
||||||
|
|
||||||
|
// 打开文件
|
||||||
|
// path : 文件路径
|
||||||
|
// append : true 追加写;false 清空重写
|
||||||
|
bool open(const std::string& path, bool append);
|
||||||
|
|
||||||
|
// 关闭文件(安全可重复调用)
|
||||||
|
void close();
|
||||||
|
|
||||||
|
// 查询文件是否处于打开状态
|
||||||
|
bool isOpen() const;
|
||||||
|
|
||||||
|
// 设置滚动阈值(字节)
|
||||||
|
// bytes = 0 表示不滚动
|
||||||
|
void setRotateBytes(std::size_t bytes) { rotateBytes = bytes; }
|
||||||
|
|
||||||
|
// 写入一行,并在需要时触发滚动
|
||||||
|
void writeLine(const std::string& line) override;
|
||||||
|
|
||||||
|
// flush 文件缓冲
|
||||||
|
void flush() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 检查并执行滚动
|
||||||
|
// 返回值:是否发生滚动(或是否重新打开)
|
||||||
|
bool rotateIfNeeded();
|
||||||
|
|
||||||
|
std::ofstream ofs; // 文件输出流
|
||||||
|
std::string filePath; // 当前文件路径
|
||||||
|
bool appendMode = true; // 是否追加模式(用于 reopen)
|
||||||
|
std::size_t rotateBytes = 0; // 滚动阈值
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ========================= 日志中心 SxLogger ========================= */
|
||||||
|
// 作用:
|
||||||
|
// - 保存配置(SxLogConfig)
|
||||||
|
// - 过滤(level/tag/sink enabled)
|
||||||
|
// - 格式化前缀(时间/级别/tag/线程/源码位置)
|
||||||
|
// - 分发到 console/file 等 sink
|
||||||
|
class SxLogger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// 仅用于 Windows 控制台:把 codepage 切到 GBK,解决中文乱码。
|
||||||
|
// 不使用 WinAPI:内部通过 system("chcp 936") 实现
|
||||||
|
// 注意:这只影响终端解释输出字节的方式,不影响源码文件编码
|
||||||
|
static void setGBK();
|
||||||
|
|
||||||
|
// 获取全局单例
|
||||||
|
// 说明:函数内静态对象,C++11 起保证线程安全初始化
|
||||||
|
static SxLogger& Get();
|
||||||
|
|
||||||
|
// 设置最低输出级别
|
||||||
|
void setMinLevel(SxLogLevel level);
|
||||||
|
|
||||||
|
// 获取最低输出级别
|
||||||
|
SxLogLevel getMinLevel() const;
|
||||||
|
|
||||||
|
// 设置语言(用于 SX_T 选择)
|
||||||
|
void setLanguage(SxLogLanguage lang);
|
||||||
|
|
||||||
|
// 获取当前语言
|
||||||
|
SxLogLanguage getLanguage() const;
|
||||||
|
|
||||||
|
// 设置 Tag 过滤
|
||||||
|
// mode: None/Whitelist/Blacklist
|
||||||
|
// tags: 过滤列表(精确匹配)
|
||||||
|
void setTagFilter(SxTagFilterMode mode, const std::vector<std::string>& tags);
|
||||||
|
|
||||||
|
// 清空 Tag 过滤(恢复 None)
|
||||||
|
void clearTagFilter();
|
||||||
|
|
||||||
|
// 开关控制台输出
|
||||||
|
void enableConsole(bool enable);
|
||||||
|
|
||||||
|
// 开启文件输出
|
||||||
|
// path : 文件路径
|
||||||
|
// append : 追加写/清空写
|
||||||
|
// rotateBytes: 滚动阈值(0 不滚动)
|
||||||
|
// 返回值:是否打开成功
|
||||||
|
bool enableFile(const std::string& path, bool append = true, std::size_t rotateBytes = 0);
|
||||||
|
|
||||||
|
// 关闭文件输出(不影响控制台输出)
|
||||||
|
void disableFile();
|
||||||
|
|
||||||
|
// 快速判定是否需要输出(宏层面的短路依赖它)
|
||||||
|
// 说明:
|
||||||
|
// - shouldLog 一定要“副作用为 0”
|
||||||
|
// - 若返回 false,调用端不会创建 SxLogLine,也不会拼接字符串
|
||||||
|
bool shouldLog(SxLogLevel level, const char* tag) const;
|
||||||
|
|
||||||
|
// 输出一条完整日志
|
||||||
|
// 说明:这是统一出口,SxLogLine 析构最终会走到这里
|
||||||
|
void logLine(
|
||||||
|
SxLogLevel level,
|
||||||
|
const char* tag,
|
||||||
|
const char* file,
|
||||||
|
int line,
|
||||||
|
const char* func,
|
||||||
|
const std::string& msg);
|
||||||
|
|
||||||
|
// 获取配置副本(避免外部直接改内部 cfg)
|
||||||
|
SxLogConfig getConfigCopy() const;
|
||||||
|
|
||||||
|
// 批量设置配置(整体替换)
|
||||||
|
void setConfig(const SxLogConfig& cfg);
|
||||||
|
|
||||||
|
// 工具:把级别转为字符串(用于前缀)
|
||||||
|
static const char* levelToString(SxLogLevel level);
|
||||||
|
|
||||||
|
// 工具:生成本地时间戳字符串(用于前缀与文件滚动名)
|
||||||
|
static std::string makeTimestampLocal();
|
||||||
|
|
||||||
|
private:
|
||||||
|
SxLogger();
|
||||||
|
|
||||||
|
// 判断 tag 是否允许输出(根据 Tag 过滤模式与 tagList)
|
||||||
|
static bool tagAllowed(const SxLogConfig& cfg, const char* tag);
|
||||||
|
|
||||||
|
// 生成前缀(调用方需已持有锁)
|
||||||
|
std::string formatPrefixUnlocked(
|
||||||
|
const SxLogConfig& cfg,
|
||||||
|
SxLogLevel level,
|
||||||
|
const char* tag,
|
||||||
|
const char* file,
|
||||||
|
int line,
|
||||||
|
const char* func) const;
|
||||||
|
|
||||||
|
mutable std::mutex mtx; // 保护 cfg 与 sink 写入,确保多线程行级一致性
|
||||||
|
SxLogConfig cfg; // 当前配置
|
||||||
|
std::atomic<SxLogLanguage> lang; // 语言开关(仅影响 SX_T 选择)
|
||||||
|
|
||||||
|
std::unique_ptr<ConsoleSink> consoleSink; // 控制台 sink(enableConsole 控制)
|
||||||
|
std::unique_ptr<FileSink> fileSink; // 文件 sink(enableFile 控制)
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ========================= 双语选择辅助 ========================= */
|
||||||
|
// 说明:
|
||||||
|
// - 只做“选择 zhCN 或 enUS”,不做编码转换
|
||||||
|
// - 输出显示是否正常由终端环境决定
|
||||||
|
inline const char* SxT(const char* zhCN, const char* enUS)
|
||||||
|
{
|
||||||
|
return (SxLogger::Get().getLanguage() == SxLogLanguage::ZhCN) ? zhCN : enUS;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__cpp_char8_t) && (__cpp_char8_t >= 201811L)
|
||||||
|
// 说明:
|
||||||
|
// - C++20 的 u8"xxx" 是 char8_t*,为了兼容调用端,这里提供重载
|
||||||
|
// - reinterpret_cast 只是改指针类型,不做 UTF-8 -> GBK 转码
|
||||||
|
inline const char* SxT(const char8_t* zhCN, const char* enUS)
|
||||||
|
{
|
||||||
|
return (SxLogger::Get().getLanguage() == SxLogLanguage::ZhCN)
|
||||||
|
? reinterpret_cast<const char*>(zhCN)
|
||||||
|
: enUS;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* ========================= RAII 日志行对象 ========================= */
|
||||||
|
// 作用:
|
||||||
|
// - 构造时记录 level/tag/源码位置
|
||||||
|
// - operator<< 拼接内容
|
||||||
|
// - 析构时统一提交给 SxLogger::logLine 输出
|
||||||
|
//
|
||||||
|
// 设计意义:
|
||||||
|
// - 避免调用端忘记写换行
|
||||||
|
// - 保证一行日志作为整体写出
|
||||||
|
class SxLogLine
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// 构造:记录元信息(不输出)
|
||||||
|
SxLogLine(SxLogLevel level, const char* tag, const char* file, int line, const char* func);
|
||||||
|
|
||||||
|
// 析构:提交输出(真正写出发生在这里)
|
||||||
|
~SxLogLine();
|
||||||
|
|
||||||
|
// 拼接内容(流式写法)
|
||||||
|
template<typename T>
|
||||||
|
SxLogLine& operator<<(const T& v)
|
||||||
|
{
|
||||||
|
ss << v;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SxLogLevel lvl; // 日志级别
|
||||||
|
const char* tg; // Tag(不拥有内存)
|
||||||
|
const char* srcFile; // 源文件名(来自 __FILE__)
|
||||||
|
int srcLine; // 行号(来自 __LINE__)
|
||||||
|
const char* srcFunc; // 函数名(来自 __func__)
|
||||||
|
std::ostringstream ss; // 内容拼接缓冲
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ========================= RAII 作用域计时对象 ========================= */
|
||||||
|
// 作用:
|
||||||
|
// - 仅在 shouldLog(Trace, tag) 为 true 时启用计时
|
||||||
|
// - 析构时输出耗时(微秒)
|
||||||
|
//
|
||||||
|
// 使用建议:
|
||||||
|
// - 只在需要定位性能瓶颈时开启 Trace
|
||||||
|
// - name 建议传入常量字符串,便于检索
|
||||||
|
class SxLogScope
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// 构造:根据 shouldLog 决定是否启用计时
|
||||||
|
SxLogScope(SxLogLevel level, const char* tag, const char* file, int line, const char* func, const char* name);
|
||||||
|
|
||||||
|
// 析构:若启用则输出耗时
|
||||||
|
~SxLogScope();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool enabled = false; // 是否启用(未启用则析构无输出)
|
||||||
|
SxLogLevel lvl = SxLogLevel::Trace; // 级别(通常用 Trace)
|
||||||
|
const char* tg = nullptr; // Tag
|
||||||
|
const char* srcFile = nullptr; // 源文件
|
||||||
|
int srcLine = 0; // 行号
|
||||||
|
const char* srcFunc = nullptr; // 函数
|
||||||
|
const char* scopeName = nullptr; // 作用域名
|
||||||
|
std::chrono::steady_clock::time_point t0; // 起始时间点
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace StellarX
|
||||||
|
|
||||||
|
#if SX_LOG_ENABLE
|
||||||
|
|
||||||
|
// SX_T:双语选择宏,调用 SxT 根据当前语言选择输出
|
||||||
|
#define SX_T(zh, en) ::StellarX::SxT(zh, en)
|
||||||
|
|
||||||
|
// 日志宏说明:
|
||||||
|
// 1) 先 shouldLog 短路过滤,未命中则不会构造 SxLogLine,也不会执行 else 分支的表达式
|
||||||
|
// 2) 命中则构造临时 SxLogLine,并允许继续使用 operator<< 拼接
|
||||||
|
// 3) 语句结束时临时对象析构,触发真正输出
|
||||||
|
#define SX_LOG_TRACE(tag) if(!::StellarX::SxLogger::Get().shouldLog(::StellarX::SxLogLevel::Trace, tag)) ; else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Trace, tag, __FILE__, __LINE__, __func__)
|
||||||
|
#define SX_LOGD(tag) if(!::StellarX::SxLogger::Get().shouldLog(::StellarX::SxLogLevel::Debug, tag)) ; else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Debug, tag, __FILE__, __LINE__, __func__)
|
||||||
|
#define SX_LOGI(tag) if(!::StellarX::SxLogger::Get().shouldLog(::StellarX::SxLogLevel::Info, tag)) ; else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Info, tag, __FILE__, __LINE__, __func__)
|
||||||
|
#define SX_LOGW(tag) if(!::StellarX::SxLogger::Get().shouldLog(::StellarX::SxLogLevel::Warn, tag)) ; else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Warn, tag, __FILE__, __LINE__, __func__)
|
||||||
|
#define SX_LOGE(tag) if(!::StellarX::SxLogger::Get().shouldLog(::StellarX::SxLogLevel::Error, tag)) ; else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Error, tag, __FILE__, __LINE__, __func__)
|
||||||
|
#define SX_LOGF(tag) if(!::StellarX::SxLogger::Get().shouldLog(::StellarX::SxLogLevel::Fatal, tag)) ; else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Fatal, tag, __FILE__, __LINE__, __func__)
|
||||||
|
|
||||||
|
// 作用域耗时统计宏:默认用 Trace 级别
|
||||||
|
#define SX_TRACE_SCOPE(tag, nameLiteral) ::StellarX::SxLogScope sx_scope_##__LINE__(::StellarX::SxLogLevel::Trace, tag, __FILE__, __LINE__, __func__, nameLiteral)
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
// 关闭日志时的兼容宏:保证调用端代码不需要改动
|
||||||
|
#define SX_T(zh, en) (en)
|
||||||
|
#define SX_LOG_TRACE(tag) if(true) {} else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Off, tag, "", 0, "")
|
||||||
|
#define SX_LOGD(tag) if(true) {} else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Off, tag, "", 0, "")
|
||||||
|
#define SX_LOGI(tag) if(true) {} else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Off, tag, "", 0, "")
|
||||||
|
#define SX_LOGW(tag) if(true) {} else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Off, tag, "", 0, "")
|
||||||
|
#define SX_LOGE(tag) if(true) {} else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Off, tag, "", 0, "")
|
||||||
|
#define SX_LOGF(tag) if(true) {} else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Off, tag, "", 0, "")
|
||||||
|
#define SX_TRACE_SCOPE(tag, nameLiteral) do {} while(0)
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -25,48 +25,48 @@
|
|||||||
#define BUTMINWIDTH 30
|
#define BUTMINWIDTH 30
|
||||||
class TabControl :public Canvas
|
class TabControl :public Canvas
|
||||||
{
|
{
|
||||||
int tabBarHeight = BUTMINWIDTH; //页签栏高度
|
int tabBarHeight = BUTMINWIDTH; //页签栏高度
|
||||||
StellarX::TabPlacement tabPlacement = StellarX::TabPlacement::Top ; //页签排列方式
|
bool IsFirstDraw = true; //首次绘制标记
|
||||||
std::vector<std::pair<std::unique_ptr<Button>,std::unique_ptr<Canvas>>> controls; //页签/页列表
|
int defaultActivation = -1; //默认激活页签索引
|
||||||
|
StellarX::TabPlacement tabPlacement = StellarX::TabPlacement::Top; //页签排列方式
|
||||||
|
std::vector<std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>> controls; //页签/页列表
|
||||||
|
|
||||||
private:
|
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:
|
||||||
TabControl();
|
TabControl();
|
||||||
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;
|
||||||
|
|
||||||
void draw() override;
|
void draw() override;
|
||||||
bool handleEvent(const ExMessage& msg) override;
|
bool handleEvent(const ExMessage& msg) override;
|
||||||
|
|
||||||
//添加页签+页
|
//添加页签+页
|
||||||
void add(std::pair<std::unique_ptr<Button> ,std::unique_ptr<Canvas>>&& control);
|
void add(std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>&& control);
|
||||||
//添加为某个页添加控件
|
//添加为某个页添加控件
|
||||||
void add(std::string tabText,std::unique_ptr<Control> control);
|
void add(std::string tabText, std::unique_ptr<Control> control);
|
||||||
//设置页签位置
|
//设置页签位置
|
||||||
void setTabPlacement(StellarX::TabPlacement placement);
|
void setTabPlacement(StellarX::TabPlacement placement);
|
||||||
//设置页签栏高度 两侧排列时为宽度
|
//设置页签栏高度 两侧排列时为宽度
|
||||||
void setTabBarHeight(int height);
|
void setTabBarHeight(int height);
|
||||||
//设置不可见后传递给子控件重写
|
//设置不可见后传递给子控件重写
|
||||||
void setIsVisible(bool visible) override;
|
void setIsVisible(bool visible) override;
|
||||||
void onWindowResize() override;
|
void onWindowResize() override;
|
||||||
//获取当前激活页签索引
|
//获取当前激活页签索引
|
||||||
int getActiveIndex() const;
|
int getActiveIndex() const;
|
||||||
//设置当前激活页签索引
|
//设置当前激活页签索引
|
||||||
void setActiveIndex(int idx);
|
void setActiveIndex(int idx);
|
||||||
//获取页签数量
|
//获取页签数量
|
||||||
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;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,28 +21,26 @@
|
|||||||
|
|
||||||
class Label : public Control
|
class Label : public Control
|
||||||
{
|
{
|
||||||
std::string text; //标签文本
|
std::string text; //标签文本
|
||||||
COLORREF textBkColor; //标签背景颜色
|
COLORREF textBkColor; //标签背景颜色
|
||||||
bool textBkDisap = false; //标签背景是否透明
|
bool textBkDisap = false; //标签背景是否透明
|
||||||
|
|
||||||
//标签事件处理(标签无事件)不实现具体代码
|
//标签事件处理(标签无事件)不实现具体代码
|
||||||
bool handleEvent(const ExMessage& msg) override { return false; }
|
bool handleEvent(const ExMessage& msg) override { return false; }
|
||||||
//用来检查对话框是否模态,此控件不做实现
|
//用来检查对话框是否模态,此控件不做实现
|
||||||
bool model() const override { return false; };
|
bool model() const override { return false; };
|
||||||
public:
|
public:
|
||||||
StellarX::ControlText textStyle; //标签文本样式
|
StellarX::ControlText textStyle; //标签文本样式
|
||||||
public:
|
public:
|
||||||
Label();
|
Label();
|
||||||
Label(int x, int y, std::string text = "标签",COLORREF textcolor = BLACK, COLORREF bkColor= RGB(255,255,255));
|
Label(int x, int y, std::string text = "标签", COLORREF textcolor = BLACK, COLORREF bkColor = RGB(255, 255, 255));
|
||||||
|
|
||||||
void draw() override;
|
|
||||||
void hide();
|
|
||||||
//设置标签背景是否透明
|
|
||||||
void setTextdisap(bool key);
|
|
||||||
//设置标签背景颜色
|
|
||||||
void setTextBkColor(COLORREF color);
|
|
||||||
//设置标签文本
|
|
||||||
void setText(std::string text);
|
|
||||||
|
|
||||||
|
void draw() override;
|
||||||
|
void hide();
|
||||||
|
//设置标签背景是否透明
|
||||||
|
void setTextdisap(bool key);
|
||||||
|
//设置标签背景颜色
|
||||||
|
void setTextBkColor(COLORREF color);
|
||||||
|
//设置标签文本
|
||||||
|
void setText(std::string text);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,6 @@
|
|||||||
#define TABLE_STR_PAGE_MID "页/共"
|
#define TABLE_STR_PAGE_MID "页/共"
|
||||||
#define TABLE_STR_PAGE_SUFFIX "页"
|
#define TABLE_STR_PAGE_SUFFIX "页"
|
||||||
|
|
||||||
|
|
||||||
class Table :public Control
|
class Table :public Control
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
@@ -64,16 +63,16 @@ private:
|
|||||||
|
|
||||||
int rowsPerPage = TABLE_DEFAULT_ROWS_PER_PAGE; // 每页显示的行数
|
int rowsPerPage = TABLE_DEFAULT_ROWS_PER_PAGE; // 每页显示的行数
|
||||||
int currentPage = 1; // 当前页码
|
int currentPage = 1; // 当前页码
|
||||||
int totalPages = 1; // 总页数
|
int totalPages = 1; // 总页数
|
||||||
|
|
||||||
bool isShowPageButton = true; // 是否显示翻页按钮
|
bool isShowPageButton = true; // 是否显示翻页按钮
|
||||||
bool isNeedDrawHeaders = true; // 是否需要绘制表头
|
bool isNeedDrawHeaders = true; // 是否需要绘制表头(暂时废弃,单做保留,后期优化可能用到)
|
||||||
bool isNeedCellSize = true; // 是否需要计算单元格尺寸
|
bool isNeedCellSize = true; // 是否需要计算单元格尺寸
|
||||||
bool isNeedButtonAndPageNum = true; // 是否需要计算翻页按钮和页码信息
|
bool isNeedButtonAndPageNum = true; // 是否需要计算翻页按钮和页码信息
|
||||||
|
|
||||||
Button* prevButton = nullptr; // 上一页按钮
|
Button* prevButton = nullptr; // 上一页按钮
|
||||||
Button* nextButton = nullptr; // 下一页按钮
|
Button* nextButton = nullptr; // 下一页按钮
|
||||||
Label* pageNum = nullptr; //页码文本
|
Label* pageNum = nullptr; //页码文本
|
||||||
|
|
||||||
int dX = x, dY = y; // 单元格的开始坐标
|
int dX = x, dY = y; // 单元格的开始坐标
|
||||||
int uX = x, uY = y; // 单元格的结束坐标
|
int uX = x, uY = y; // 单元格的结束坐标
|
||||||
@@ -131,6 +130,12 @@ public:
|
|||||||
void setTableLineStyle(StellarX::LineStyle style);
|
void setTableLineStyle(StellarX::LineStyle style);
|
||||||
//设置边框宽度
|
//设置边框宽度
|
||||||
void setTableBorderWidth(int width);
|
void setTableBorderWidth(int width);
|
||||||
|
//清空表头
|
||||||
|
void clearHeaders();
|
||||||
|
//清空表格数据
|
||||||
|
void clearData();
|
||||||
|
//清空表头和数据
|
||||||
|
void resetTable();
|
||||||
//窗口变化丢快照+标脏
|
//窗口变化丢快照+标脏
|
||||||
void onWindowResize() override;
|
void onWindowResize() override;
|
||||||
|
|
||||||
@@ -153,7 +158,7 @@ public:
|
|||||||
//获取线型
|
//获取线型
|
||||||
StellarX::LineStyle getTableLineStyle() const;
|
StellarX::LineStyle getTableLineStyle() const;
|
||||||
//获取表头
|
//获取表头
|
||||||
std::vector<std::string> getHeaders () const;
|
std::vector<std::string> getHeaders() const;
|
||||||
//获取表格数据
|
//获取表格数据
|
||||||
std::vector<std::vector<std::string>> getData() const;
|
std::vector<std::vector<std::string>> getData() const;
|
||||||
//获取表格边框宽度
|
//获取表格边框宽度
|
||||||
@@ -161,7 +166,4 @@ public:
|
|||||||
//获取表格尺寸
|
//获取表格尺寸
|
||||||
int getTableWidth() const;
|
int getTableWidth() const;
|
||||||
int getTableHeight() const;
|
int getTableHeight() const;
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,42 +18,39 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "Control.h"
|
#include "Control.h"
|
||||||
|
|
||||||
|
|
||||||
class TextBox : public Control
|
class TextBox : public Control
|
||||||
{
|
{
|
||||||
std::string text; //文本
|
std::string text; //文本
|
||||||
StellarX::TextBoxmode mode; //模式
|
StellarX::TextBoxmode mode; //模式
|
||||||
StellarX::ControlShape shape; //形状
|
StellarX::ControlShape shape; //形状
|
||||||
bool click = false; //是否点击
|
bool click = false; //是否点击
|
||||||
size_t maxCharLen = 10;//最大字符长度
|
size_t maxCharLen = 10;//最大字符长度
|
||||||
COLORREF textBoxBkClor = RGB(255, 255, 255); //背景颜色
|
COLORREF textBoxBkClor = RGB(255, 255, 255); //背景颜色
|
||||||
COLORREF textBoxBorderClor = RGB(0,0,0); //边框颜色
|
COLORREF textBoxBorderClor = RGB(0, 0, 0); //边框颜色
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StellarX::ControlText textStyle; //文本样式
|
StellarX::ControlText textStyle; //文本样式
|
||||||
|
|
||||||
TextBox(int x, int y, int width, int height, std::string text = "", StellarX::TextBoxmode mode = StellarX::TextBoxmode::INPUT_MODE, StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
|
TextBox(int x, int y, int width, int height, std::string text = "", StellarX::TextBoxmode mode = StellarX::TextBoxmode::INPUT_MODE, StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
|
||||||
void draw() override;
|
void draw() override;
|
||||||
bool handleEvent(const ExMessage& msg) override;
|
bool handleEvent(const ExMessage& msg) override;
|
||||||
//设置模式
|
//设置模式
|
||||||
void setMode(StellarX::TextBoxmode mode);
|
void setMode(StellarX::TextBoxmode mode);
|
||||||
//设置可输入最大字符长度
|
//设置可输入最大字符长度
|
||||||
void setMaxCharLen(size_t len);
|
void setMaxCharLen(size_t len);
|
||||||
//设置形状
|
//设置形状
|
||||||
void setTextBoxshape(StellarX::ControlShape shape);
|
void setTextBoxshape(StellarX::ControlShape shape);
|
||||||
//设置边框颜色
|
//设置边框颜色
|
||||||
void setTextBoxBorder(COLORREF color);
|
void setTextBoxBorder(COLORREF color);
|
||||||
//设置背景颜色
|
//设置背景颜色
|
||||||
void setTextBoxBk(COLORREF color);
|
void setTextBoxBk(COLORREF color);
|
||||||
//设置文本
|
//设置文本
|
||||||
void setText(std::string text);
|
void setText(std::string text);
|
||||||
|
|
||||||
//获取文本
|
//获取文本
|
||||||
std::string getText() const;
|
std::string getText() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//用来检查对话框是否模态,此控件不做实现
|
//用来检查对话框是否模态,此控件不做实现
|
||||||
bool model() const override { return false; };
|
bool model() const override { return false; };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
|
/**
|
||||||
/**
|
|
||||||
* Window(头文件)
|
* Window(头文件)
|
||||||
*
|
*
|
||||||
* 设计目标:
|
* 设计目标:
|
||||||
@@ -13,6 +12,7 @@
|
|||||||
* - WM_GETMINMAXINFO:按最小“客户区”换算到“窗口矩形”,提供系统层最小轨迹值。
|
* - WM_GETMINMAXINFO:按最小“客户区”换算到“窗口矩形”,提供系统层最小轨迹值。
|
||||||
* - runEventLoop:只记录 WM_SIZE 的新尺寸;真正绘制放在 needResizeDirty 时集中处理。
|
* - runEventLoop:只记录 WM_SIZE 的新尺寸;真正绘制放在 needResizeDirty 时集中处理。
|
||||||
*/
|
*/
|
||||||
|
//fuck windows fuck win32
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Control.h"
|
#include "Control.h"
|
||||||
@@ -23,88 +23,73 @@
|
|||||||
|
|
||||||
class Window
|
class Window
|
||||||
{
|
{
|
||||||
// —— 尺寸状态 ——(绘制尺寸与待应用尺寸分离;收口时一次性更新)
|
// —— 尺寸状态 ——(绘制尺寸与待应用尺寸分离;收口时一次性更新)
|
||||||
int width; // 当前有效宽(已应用到画布/控件的客户区宽)
|
int width; // 当前有效宽(已应用到画布/控件的客户区宽)
|
||||||
int height; // 当前有效高(已应用到画布/控件的客户区高)
|
int height; // 当前有效高(已应用到画布/控件的客户区高)
|
||||||
int localwidth; // 基准宽(创建时的宽度)
|
int localwidth; // 基准宽(创建时的宽度)
|
||||||
int localheight; // 基准高(创建是的高度)
|
int localheight; // 基准高(创建是的高度)
|
||||||
int pendingW; // 待应用宽(WM_SIZE/拉伸中记录用)
|
int pendingW; // 待应用宽(WM_SIZE/拉伸中记录用)
|
||||||
int pendingH; // 待应用高
|
int pendingH; // 待应用高
|
||||||
int minClientW; // 业务设定的最小客户区宽(用于 GETMINMAXINFO 与 SIZING 夹紧)
|
int minClientW; // 业务设定的最小客户区宽(用于 GETMINMAXINFO 与 SIZING 夹紧)
|
||||||
int minClientH; // 业务设定的最小客户区高
|
int minClientH; // 业务设定的最小客户区高
|
||||||
int windowMode = NULL; // EasyX 初始化模式(EX_SHOWCONSOLE/EX_TOPMOST/...)
|
int windowMode = NULL; // EasyX 初始化模式(EX_SHOWCONSOLE/EX_TOPMOST/...)
|
||||||
bool needResizeDirty = false; // 统一收口重绘标志(置位后在事件环末尾处理)
|
bool needResizeDirty = false; // 统一收口重绘标志(置位后在事件环末尾处理)
|
||||||
bool isSizing = false; // 是否处于拖拽阶段(ENTER/EXIT SIZEMOVE 切换)
|
bool isSizing = false; // 是否处于拖拽阶段(ENTER/EXIT SIZEMOVE 切换)
|
||||||
|
|
||||||
// —— 原生窗口句柄与子类化钩子 ——(子类化 EasyX 的窗口过程以拦截关键消息)
|
// —— 原生窗口句柄与子类化钩子 ——(子类化 EasyX 的窗口过程以拦截关键消息)
|
||||||
HWND hWnd = NULL; // EasyX 初始化后的窗口句柄
|
HWND hWnd = NULL; // EasyX 初始化后的窗口句柄
|
||||||
WNDPROC oldWndProc = nullptr; // 保存旧过程(CallWindowProc 回落)
|
WNDPROC oldWndProc = nullptr; // 保存旧过程(CallWindowProc 回落)
|
||||||
bool procHooked = false; // 避免重复子类化
|
bool procHooked = false; // 避免重复子类化
|
||||||
static LRESULT CALLBACK WndProcThunk(HWND h, UINT m, WPARAM w, LPARAM l); // 静态过程分发到 this
|
static LRESULT CALLBACK WndProcThunk(HWND h, UINT m, WPARAM w, LPARAM l); // 静态过程分发到 this
|
||||||
|
|
||||||
// —— 绘制相关 ——(是否使用合成双缓冲、窗口标题、背景等)
|
// —— 绘制相关 ——(是否使用合成双缓冲、窗口标题、背景等)
|
||||||
bool useComposited = true; // 是否启用 WS_EX_COMPOSITED(部分机器可能增加一帧观感延迟)
|
bool useComposited = true; // 是否启用 WS_EX_COMPOSITED(部分机器可能增加一帧观感延迟)
|
||||||
std::string headline; // 窗口标题文本
|
std::string headline; // 窗口标题文本
|
||||||
COLORREF wBkcolor = BLACK; // 纯色背景(无背景图时使用)
|
COLORREF wBkcolor = BLACK; // 纯色背景(无背景图时使用)
|
||||||
IMAGE* background = nullptr; // 背景图对象指针(存在时优先绘制)
|
IMAGE* background = nullptr; // 背景图对象指针(存在时优先绘制)
|
||||||
std::string bkImageFile; // 背景图文件路径(loadimage 用)
|
std::string bkImageFile; // 背景图文件路径(loadimage 用)
|
||||||
|
|
||||||
// —— 控件/对话框 ——(容器内的普通控件与非模态对话框)
|
// —— 控件/对话框 ——(容器内的普通控件与非模态对话框)
|
||||||
std::vector<std::unique_ptr<Control>> controls;
|
std::vector<std::unique_ptr<Control>> controls;
|
||||||
std::vector<std::unique_ptr<Control>> dialogs;
|
std::vector<std::unique_ptr<Control>> dialogs;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool dialogClose = false; // 项目内使用的状态位
|
bool dialogClose = false; // 项目内使用的状态位,对话框关闭标志
|
||||||
|
|
||||||
// —— 构造/析构 ——(仅初始化成员;实际样式与子类化在 draw() 中完成)
|
// —— 构造/析构 ——(仅初始化成员;实际样式与子类化在 draw() 中完成)
|
||||||
Window(int width, int height, int mode);
|
Window(int width, int height, int mode);
|
||||||
Window(int width, int height, int mode, COLORREF bkcloc);
|
Window(int width, int height, int mode, COLORREF bkcloc);
|
||||||
Window(int width, int height, int mode, COLORREF bkcloc, std::string headline);
|
Window(int width, int height, int mode, COLORREF bkcloc, std::string headline);
|
||||||
~Window();
|
~Window();
|
||||||
|
|
||||||
// —— 绘制与事件循环 ——(draw* 完成一次全量绘制;runEventLoop 驱动事件与统一收口)
|
// —— 绘制与事件循环 ——(draw* 完成一次全量绘制;runEventLoop 驱动事件与统一收口)
|
||||||
void draw(); // 纯色背景版本
|
void draw(); // 纯色背景版本
|
||||||
void draw(std::string pImgFile); // 背景图版本
|
void draw(std::string pImgFile); // 背景图版本
|
||||||
int runEventLoop(); // 主事件循环(PeekMessage + 统一收口重绘)
|
int runEventLoop(); // 主事件循环(PeekMessage + 统一收口重绘)
|
||||||
|
|
||||||
// —— 背景/标题设置 ——(更换背景、背景色与标题;立即触发一次批量绘制)
|
// —— 背景/标题设置 ——(更换背景、背景色与标题;立即触发一次批量绘制)
|
||||||
void setBkImage(std::string pImgFile);
|
void setBkImage(std::string pImgFile);
|
||||||
void setBkcolor(COLORREF c);
|
void setBkcolor(COLORREF c);
|
||||||
void setHeadline(std::string headline);
|
void setHeadline(std::string headline);
|
||||||
|
|
||||||
// —— 控件/对话框管理 ——(添加到容器,或做存在性判断)
|
// —— 控件/对话框管理 ——(添加到容器,或做存在性判断)
|
||||||
void addControl(std::unique_ptr<Control> control);
|
void addControl(std::unique_ptr<Control> control);
|
||||||
void addDialog(std::unique_ptr<Control> dialogs);
|
void addDialog(std::unique_ptr<Control> dialogs);
|
||||||
bool hasNonModalDialogWithCaption(const std::string& caption, const std::string& message) const;
|
bool hasNonModalDialogWithCaption(const std::string& caption, const std::string& message) const;
|
||||||
|
|
||||||
// —— 访问器 ——(只读接口,供外部查询当前窗口/标题/背景等)
|
// —— 访问器 ——(只读接口,供外部查询当前窗口/标题/背景等)
|
||||||
HWND getHwnd() const;
|
HWND getHwnd() const;
|
||||||
int getWidth() const;
|
int getWidth() const;
|
||||||
int getHeight() const;
|
int getHeight() const;
|
||||||
std::string getHeadline() const;
|
std::string getHeadline() const;
|
||||||
COLORREF getBkcolor() const;
|
COLORREF getBkcolor() const;
|
||||||
IMAGE* getBkImage() const;
|
IMAGE* getBkImage() const;
|
||||||
std::string getBkImageFile() const;
|
std::string getBkImageFile() const;
|
||||||
std::vector<std::unique_ptr<Control>>& getControls();
|
std::vector<std::unique_ptr<Control>>& getControls();
|
||||||
|
|
||||||
// —— 配置开关 ——(动态调整最小客户区、合成双缓冲)
|
// —— 尺寸调整 ——(供内部与外部调用的尺寸变化处理)
|
||||||
inline void setMinClientSize(int w, int h)
|
void pumpResizeIfNeeded(); // 执行一次统一收口重绘
|
||||||
{
|
void scheduleResizeFromModal(int w, int h);
|
||||||
// 仅更新阈值;实际约束在 WM_GETMINMAXINFO/WM_SIZING 中生效
|
|
||||||
minClientW = w;
|
|
||||||
minClientH = h;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void setComposited(bool on)
|
|
||||||
{
|
|
||||||
// 更新标志;真正应用在 draw()/样式 SetWindowLongEx + SWP_FRAMECHANGED
|
|
||||||
useComposited = on;
|
|
||||||
}
|
|
||||||
|
|
||||||
void processWindowMessage(const ExMessage & msg); // 处理 EX_WINDOW 中的 WM_SIZE 等
|
|
||||||
void pumpResizeIfNeeded(); // 执行一次统一收口重绘
|
|
||||||
void scheduleResizeFromModal(int w, int h);
|
|
||||||
private:
|
private:
|
||||||
void adaptiveLayout(std::unique_ptr<Control>& c,const int finalH, const int finalW);
|
void adaptiveLayout(std::unique_ptr<Control>& c, const int finalH, const int finalW);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "Button.h"
|
#include "Button.h"
|
||||||
|
#include "SxLog.h"
|
||||||
|
|
||||||
Button::Button(int x, int y, int width, int height, const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape)
|
Button::Button(int x, int y, int width, int height, const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape)
|
||||||
: Control(x, y, width, height)
|
: Control(x, y, width, height)
|
||||||
@@ -163,7 +164,6 @@ void Button::initButton(const std::string text, StellarX::ButtonMode mode, Stell
|
|||||||
tipLabel.textStyle = this->textStyle; // 复用按钮字体样式
|
tipLabel.textStyle = this->textStyle; // 复用按钮字体样式
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Button::~Button()
|
Button::~Button()
|
||||||
{
|
{
|
||||||
if (buttonFileIMAGE)
|
if (buttonFileIMAGE)
|
||||||
@@ -274,7 +274,6 @@ void Button::draw()
|
|||||||
|
|
||||||
restoreStyle();//恢复默认字体样式和颜色
|
restoreStyle();//恢复默认字体样式和颜色
|
||||||
dirty = false; //标记按钮不需要重绘
|
dirty = false; //标记按钮不需要重绘
|
||||||
|
|
||||||
}
|
}
|
||||||
// 处理鼠标事件,检测点击和悬停状态
|
// 处理鼠标事件,检测点击和悬停状态
|
||||||
// 根据按钮模式和形状进行不同的处理
|
// 根据按钮模式和形状进行不同的处理
|
||||||
@@ -283,8 +282,9 @@ bool Button::handleEvent(const ExMessage& msg)
|
|||||||
if (!show)
|
if (!show)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
bool oldHover = hover;
|
bool oldHover = hover;// 注意:只在状态变化时记录,避免 WM_MOUSEMOVE 刷屏
|
||||||
bool oldClick = click;
|
bool oldClick = click;
|
||||||
|
|
||||||
bool consume = false;//是否消耗事件
|
bool consume = false;//是否消耗事件
|
||||||
// 记录鼠标位置(用于tip定位)
|
// 记录鼠标位置(用于tip定位)
|
||||||
if (msg.message == WM_MOUSEMOVE)
|
if (msg.message == WM_MOUSEMOVE)
|
||||||
@@ -310,14 +310,19 @@ bool Button::handleEvent(const ExMessage& msg)
|
|||||||
hover = isMouseInEllipse(msg.x, msg.y, x, y, x + width, y + height);
|
hover = isMouseInEllipse(msg.x, msg.y, x, y, x + width, y + height);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (hover != oldHover)
|
||||||
|
{
|
||||||
|
SX_LOGD("Button") << SX_T("悬停变化: ","hover change: ") << "id=" << id
|
||||||
|
<< " " << (oldHover ? 1 : 0) << "->" << (hover ? 1 : 0);
|
||||||
|
}
|
||||||
// 处理鼠标点击事件
|
// 处理鼠标点击事件
|
||||||
if (msg.message == WM_LBUTTONDOWN && hover && mode != StellarX::ButtonMode::DISABLED)
|
if (msg.message == WM_LBUTTONDOWN && hover && mode != StellarX::ButtonMode::DISABLED)
|
||||||
{
|
{
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
dirty = true;
|
dirty = true;
|
||||||
consume = true;
|
consume = true;
|
||||||
}
|
}
|
||||||
@@ -334,6 +339,8 @@ bool Button::handleEvent(const ExMessage& msg)
|
|||||||
if (mode == StellarX::ButtonMode::NORMAL && click)
|
if (mode == StellarX::ButtonMode::NORMAL && click)
|
||||||
{
|
{
|
||||||
if (onClickCallback) onClickCallback();
|
if (onClickCallback) onClickCallback();
|
||||||
|
SX_LOGI("Button") << "click: id=" << id << " (NORMAL) callback=" << (onClickCallback ? "Y" : "N");
|
||||||
|
|
||||||
click = false;
|
click = false;
|
||||||
dirty = true;
|
dirty = true;
|
||||||
consume = true;
|
consume = true;
|
||||||
@@ -346,6 +353,11 @@ bool Button::handleEvent(const ExMessage& msg)
|
|||||||
click = !click;
|
click = !click;
|
||||||
if (click && onToggleOnCallback) onToggleOnCallback();
|
if (click && onToggleOnCallback) onToggleOnCallback();
|
||||||
else if (!click && onToggleOffCallback) onToggleOffCallback();
|
else if (!click && onToggleOffCallback) onToggleOffCallback();
|
||||||
|
SX_LOGI("Button") << "toggle: id=" << id
|
||||||
|
<< " " << (oldClick ? 1 : 0) << "->" << (click ? 1 : 0)
|
||||||
|
<< " onCb=" << (onToggleOnCallback ? "Y" : "N")
|
||||||
|
<< " offCb=" << (onToggleOffCallback ? "Y" : "N");
|
||||||
|
|
||||||
dirty = true;
|
dirty = true;
|
||||||
consume = true;
|
consume = true;
|
||||||
refreshTooltipTextForState();
|
refreshTooltipTextForState();
|
||||||
@@ -385,6 +397,8 @@ bool Button::handleEvent(const ExMessage& msg)
|
|||||||
// 到点就显示
|
// 到点就显示
|
||||||
if (GetTickCount64() - tipHoverTick >= (ULONGLONG)tipDelayMs)
|
if (GetTickCount64() - tipHoverTick >= (ULONGLONG)tipDelayMs)
|
||||||
{
|
{
|
||||||
|
SX_LOGD("Button") << SX_T("提示信息显示: ","tooltip show:")<<" id = " << id <<SX_T("延时时间: ", " delayMs = ") << tipDelayMs;
|
||||||
|
|
||||||
tipVisible = true;
|
tipVisible = true;
|
||||||
|
|
||||||
// 定位(跟随鼠标 or 相对按钮)
|
// 定位(跟随鼠标 or 相对按钮)
|
||||||
@@ -451,7 +465,6 @@ void Button::setROUND_RECTANGLEwidth(int width)
|
|||||||
{
|
{
|
||||||
rouRectangleSize.ROUND_RECTANGLEwidth = width;
|
rouRectangleSize.ROUND_RECTANGLEwidth = width;
|
||||||
this->dirty = true; // 标记需要重绘
|
this->dirty = true; // 标记需要重绘
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Button::setROUND_RECTANGLEheight(int height)
|
void Button::setROUND_RECTANGLEheight(int height)
|
||||||
@@ -489,7 +502,6 @@ void Button::setFillIma(std::string imaNAme)
|
|||||||
this->dirty = true;
|
this->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Button::setButtonBorder(COLORREF Border)
|
void Button::setButtonBorder(COLORREF Border)
|
||||||
{
|
{
|
||||||
buttonBorderColor = Border;
|
buttonBorderColor = Border;
|
||||||
@@ -558,7 +570,6 @@ void Button::setButtonClick(BOOL click)
|
|||||||
requestRepaint(parent);
|
requestRepaint(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string Button::getButtonText() const
|
std::string Button::getButtonText() const
|
||||||
{
|
{
|
||||||
return this->text;
|
return this->text;
|
||||||
@@ -619,8 +630,6 @@ int Button::getButtonHeight() const
|
|||||||
return this->height;
|
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));
|
||||||
@@ -666,11 +675,9 @@ void Button::cutButtonText()
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
cutText = ellipsize_cjk_pref(this->text, contentW, "…"); // 全角省略号
|
cutText = ellipsize_cjk_pref(this->text, contentW, "…"); // 全角省略号
|
||||||
|
|
||||||
}
|
}
|
||||||
isUseCutText = true;
|
isUseCutText = true;
|
||||||
needCutText = false;
|
needCutText = false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Button::hideTooltip()
|
void Button::hideTooltip()
|
||||||
@@ -691,6 +698,3 @@ void Button::refreshTooltipTextForState()
|
|||||||
else if (mode == StellarX::ButtonMode::TOGGLE)
|
else if (mode == StellarX::ButtonMode::TOGGLE)
|
||||||
tipLabel.setText(click ? tipTextOn : tipTextOff);
|
tipLabel.setText(click ? tipTextOn : tipTextOff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
473
src/Canvas.cpp
473
src/Canvas.cpp
@@ -1,4 +1,10 @@
|
|||||||
#include "Canvas.h"
|
#include "Canvas.h"
|
||||||
|
#include "SxLog.h"
|
||||||
|
|
||||||
|
static bool SxIsNoisyMsg(UINT m)
|
||||||
|
{
|
||||||
|
return m == WM_MOUSEMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
Canvas::Canvas()
|
Canvas::Canvas()
|
||||||
:Control(0, 0, 100, 100)
|
:Control(0, 0, 100, 100)
|
||||||
@@ -14,24 +20,24 @@ Canvas::Canvas(int x, int y, int width, int height)
|
|||||||
|
|
||||||
void Canvas::setX(int x)
|
void Canvas::setX(int x)
|
||||||
{
|
{
|
||||||
this->x = x;
|
this->x = x;
|
||||||
for (auto& c : controls)
|
for (auto& c : controls)
|
||||||
{
|
{
|
||||||
c->onWindowResize();
|
c->onWindowResize();
|
||||||
c->setX(c->getLocalX() + this->x);
|
c->setX(c->getLocalX() + this->x);
|
||||||
}
|
}
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Canvas::setY(int y)
|
void Canvas::setY(int y)
|
||||||
{
|
{
|
||||||
this->y = y;
|
this->y = y;
|
||||||
for (auto& c : controls)
|
for (auto& c : controls)
|
||||||
{
|
{
|
||||||
c->onWindowResize();
|
c->onWindowResize();
|
||||||
c->setY(c->getLocalY() + this->y);
|
c->setY(c->getLocalY() + this->y);
|
||||||
}
|
}
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Canvas::clearAllControls()
|
void Canvas::clearAllControls()
|
||||||
@@ -50,38 +56,38 @@ void Canvas::draw()
|
|||||||
}
|
}
|
||||||
saveStyle();
|
saveStyle();
|
||||||
setlinecolor(canvasBorderClor);//设置线色
|
setlinecolor(canvasBorderClor);//设置线色
|
||||||
if(StellarX::FillMode::Null != canvasFillMode)
|
if (StellarX::FillMode::Null != canvasFillMode)
|
||||||
setfillcolor(canvasBkClor);//设置填充色
|
setfillcolor(canvasBkClor);//设置填充色
|
||||||
setfillstyle((int)canvasFillMode);//设置填充模式
|
setfillstyle((int)canvasFillMode);//设置填充模式
|
||||||
setlinestyle((int)canvasLineStyle, canvaslinewidth);
|
setlinestyle((int)canvasLineStyle, canvaslinewidth);
|
||||||
|
|
||||||
// 在绘制画布之前,先恢复并更新背景快照:
|
// 在绘制画布之前,先恢复并更新背景快照:
|
||||||
// 1. 如果已有快照,则先回贴旧快照以清除之前的内容。
|
// 1. 如果已有快照,则先回贴旧快照以清除之前的内容。
|
||||||
// 2. 当坐标或尺寸变化,或缓存图像无效时,丢弃旧快照并重新抓取新的背景。
|
// 2. 当坐标或尺寸变化,或缓存图像无效时,丢弃旧快照并重新抓取新的背景。
|
||||||
int margin = canvaslinewidth > 1 ? canvaslinewidth : 1;
|
int margin = canvaslinewidth > 1 ? canvaslinewidth : 1;
|
||||||
if (hasSnap)
|
if (hasSnap)
|
||||||
{
|
{
|
||||||
// 恢复旧快照,清除上一次绘制
|
// 恢复旧快照,清除上一次绘制
|
||||||
restBackground();
|
restBackground();
|
||||||
// 如果位置或尺寸变了,或没有有效缓存,则重新抓取
|
// 如果位置或尺寸变了,或没有有效缓存,则重新抓取
|
||||||
if (!saveBkImage || saveBkX != this->x - margin || saveBkY != this->y - margin || saveWidth != this->width + margin * 2 || saveHeight != this->height + margin * 2)
|
if (!saveBkImage || saveBkX != this->x - margin || saveBkY != this->y - margin || saveWidth != this->width + margin * 2 || saveHeight != this->height + margin * 2)
|
||||||
{
|
{
|
||||||
discardBackground();
|
discardBackground();
|
||||||
saveBackground(this->x - margin, this->y - margin, this->width + margin * 2, this->height + margin * 2);
|
saveBackground(this->x - margin, this->y - margin, this->width + margin * 2, this->height + margin * 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 首次绘制或没有快照时直接抓取背景
|
// 首次绘制或没有快照时直接抓取背景
|
||||||
saveBackground(this->x- margin, this->y- margin, this->width + margin*2, this->height + margin*2);
|
saveBackground(this->x - margin, this->y - margin, this->width + margin * 2, this->height + margin * 2);
|
||||||
}
|
}
|
||||||
// 再次恢复最新快照,确保绘制区域干净
|
// 再次恢复最新快照,确保绘制区域干净
|
||||||
restBackground();
|
restBackground();
|
||||||
//根据画布形状绘制
|
//根据画布形状绘制
|
||||||
switch (shape)
|
switch (shape)
|
||||||
{
|
{
|
||||||
case StellarX::ControlShape::RECTANGLE:
|
case StellarX::ControlShape::RECTANGLE:
|
||||||
fillrectangle(x,y,x+width,y+height);//有边框填充矩形
|
fillrectangle(x, y, x + width, y + height);//有边框填充矩形
|
||||||
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);//无边框填充矩形
|
||||||
@@ -106,19 +112,40 @@ void Canvas::draw()
|
|||||||
|
|
||||||
bool Canvas::handleEvent(const ExMessage& msg)
|
bool Canvas::handleEvent(const ExMessage& msg)
|
||||||
{
|
{
|
||||||
if (!show)return false;
|
if (!show) return false;
|
||||||
|
|
||||||
bool consumed = false;
|
bool consumed = false;
|
||||||
bool anyDirty = false;
|
bool anyDirty = false;
|
||||||
|
Control* firstConsumer = nullptr;
|
||||||
|
|
||||||
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||||
{
|
{
|
||||||
consumed |= it->get()->handleEvent(msg);
|
Control* c = it->get();
|
||||||
if (it->get()->isDirty()) anyDirty = true;
|
bool cConsumed = c->handleEvent(msg);
|
||||||
|
|
||||||
|
if (cConsumed && !firstConsumer) firstConsumer = c;
|
||||||
|
consumed |= cConsumed;
|
||||||
|
|
||||||
|
if (c->isDirty()) anyDirty = true;
|
||||||
}
|
}
|
||||||
if (anyDirty) requestRepaint(parent);
|
|
||||||
|
if (firstConsumer && !SxIsNoisyMsg(msg.message))
|
||||||
|
{
|
||||||
|
SX_LOGD("Event") << SX_T("Canvas 消耗消息: ","Canvas consumed: msg=") << msg.message
|
||||||
|
<< SX_T("子控件"," by child")<<" id=" << firstConsumer->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anyDirty)
|
||||||
|
{
|
||||||
|
if (!SxIsNoisyMsg(msg.message))
|
||||||
|
SX_LOGD("Dirty") << SX_T("Canvas检测有控件为脏状态 -> 请求重绘, ","Canvas anyDirty -> requestRepaint, ")<<"id = " << id;
|
||||||
|
requestRepaint(parent);
|
||||||
|
}
|
||||||
|
|
||||||
return consumed;
|
return consumed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Canvas::addControl(std::unique_ptr<Control> control)
|
void Canvas::addControl(std::unique_ptr<Control> control)
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -126,6 +153,16 @@ void Canvas::addControl(std::unique_ptr<Control> control)
|
|||||||
control->setX(control->getLocalX() + this->x);
|
control->setX(control->getLocalX() + this->x);
|
||||||
control->setY(control->getLocalY() + this->y);
|
control->setY(control->getLocalY() + this->y);
|
||||||
control->setParent(this);
|
control->setParent(this);
|
||||||
|
SX_LOGI("Canvas")
|
||||||
|
<< SX_T("添加子控件:父=Canvas 子id=", "addControl: parent=Canvas childId=")
|
||||||
|
<< control->getId()
|
||||||
|
<< SX_T(" 相对坐标=(", " local=(")
|
||||||
|
<< control->getLocalX() << "," << control->getLocalY()
|
||||||
|
<< SX_T(") 绝对坐标=(", ") abs=(")
|
||||||
|
<< control->getX() << "," << control->getY()
|
||||||
|
<< ")";
|
||||||
|
|
||||||
|
|
||||||
controls.push_back(std::move(control));
|
controls.push_back(std::move(control));
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
@@ -175,7 +212,6 @@ void Canvas::setCanvasLineStyle(StellarX::LineStyle style)
|
|||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Canvas::setLinewidth(int width)
|
void Canvas::setLinewidth(int width)
|
||||||
{
|
{
|
||||||
this->canvaslinewidth = width;
|
this->canvaslinewidth = width;
|
||||||
@@ -189,7 +225,6 @@ void Canvas::setIsVisible(bool visible)
|
|||||||
for (auto& control : controls)
|
for (auto& control : controls)
|
||||||
{
|
{
|
||||||
control->setIsVisible(visible);
|
control->setIsVisible(visible);
|
||||||
control->setDirty(true);
|
|
||||||
}
|
}
|
||||||
if (!visible)
|
if (!visible)
|
||||||
this->updateBackground();
|
this->updateBackground();
|
||||||
@@ -198,194 +233,214 @@ void Canvas::setIsVisible(bool visible)
|
|||||||
void Canvas::setDirty(bool dirty)
|
void Canvas::setDirty(bool dirty)
|
||||||
{
|
{
|
||||||
this->dirty = dirty;
|
this->dirty = dirty;
|
||||||
for(auto& control : controls)
|
for (auto& control : controls)
|
||||||
control->setDirty(dirty);
|
control->setDirty(dirty);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Canvas::onWindowResize()
|
void Canvas::onWindowResize()
|
||||||
{
|
{
|
||||||
// 首先处理自身的快照等逻辑
|
// 首先处理自身的快照等逻辑
|
||||||
Control::onWindowResize();
|
Control::onWindowResize();
|
||||||
|
|
||||||
// 记录父容器原始尺寸(用于计算子控件的右/下边距)
|
// 记录父容器原始尺寸(用于计算子控件的右/下边距)
|
||||||
int origParentW = this->localWidth;
|
int origParentW = this->localWidth;
|
||||||
int origParentH = this->localHeight;
|
int origParentH = this->localHeight;
|
||||||
|
|
||||||
// 当前容器的新尺寸
|
// 当前容器的新尺寸
|
||||||
int finalW = this->width;
|
int finalW = this->width;
|
||||||
int finalH = this->height;
|
int finalH = this->height;
|
||||||
|
|
||||||
// 当前容器的新坐标(全局坐标)
|
// 当前容器的新坐标(全局坐标)
|
||||||
int parentX = this->x;
|
int parentX = this->x;
|
||||||
int parentY = this->y;
|
int parentY = this->y;
|
||||||
|
|
||||||
// 调整每个子控件在 AnchorToEdges 模式下的位置与尺寸
|
// 调整每个子控件在 AnchorToEdges 模式下的位置与尺寸
|
||||||
for (auto& ch : controls)
|
for (auto& ch : controls)
|
||||||
{
|
{
|
||||||
// Only adjust when using anchor-to-edges layout
|
// Only adjust when using anchor-to-edges layout
|
||||||
if (ch->getLayoutMode() == StellarX::LayoutMode::AnchorToEdges)
|
if (ch->getLayoutMode() == StellarX::LayoutMode::AnchorToEdges)
|
||||||
{
|
{
|
||||||
// Determine whether this child is a Table; tables keep their height constant
|
// Determine whether this child is a Table; tables keep their height constant
|
||||||
bool isTable = (dynamic_cast<Table*>(ch.get()) != nullptr);
|
bool isTable = (dynamic_cast<Table*>(ch.get()) != nullptr);
|
||||||
|
|
||||||
// Unpack anchors
|
// Unpack anchors
|
||||||
auto a1 = ch->getAnchor_1();
|
auto a1 = ch->getAnchor_1();
|
||||||
auto a2 = ch->getAnchor_2();
|
auto a2 = ch->getAnchor_2();
|
||||||
|
|
||||||
bool anchorLeft = (a1 == StellarX::Anchor::Left || a2 == StellarX::Anchor::Left);
|
bool anchorLeft = (a1 == StellarX::Anchor::Left || a2 == StellarX::Anchor::Left);
|
||||||
bool anchorRight = (a1 == StellarX::Anchor::Right || a2 == StellarX::Anchor::Right);
|
bool anchorRight = (a1 == StellarX::Anchor::Right || a2 == StellarX::Anchor::Right);
|
||||||
bool anchorTop = (a1 == StellarX::Anchor::Top || a2 == StellarX::Anchor::Top);
|
bool anchorTop = (a1 == StellarX::Anchor::Top || a2 == StellarX::Anchor::Top);
|
||||||
bool anchorBottom = (a1 == StellarX::Anchor::Bottom || a2 == StellarX::Anchor::Bottom);
|
bool anchorBottom = (a1 == StellarX::Anchor::Bottom || a2 == StellarX::Anchor::Bottom);
|
||||||
|
|
||||||
// If it's a table, treat as anchored left and right horizontally and anchored top vertically by default.
|
// If it's a table, treat as anchored left and right horizontally and anchored top vertically by default.
|
||||||
if (isTable)
|
if (isTable)
|
||||||
{
|
{
|
||||||
anchorLeft = true;
|
anchorLeft = true;
|
||||||
anchorRight = true;
|
anchorRight = true;
|
||||||
// If no explicit vertical anchor was provided, default to top.
|
// If no explicit vertical anchor was provided, default to top.
|
||||||
if (!(anchorTop || anchorBottom))
|
if (!(anchorTop || anchorBottom))
|
||||||
{
|
{
|
||||||
anchorTop = true;
|
anchorTop = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute new X and width
|
// Compute new X and width
|
||||||
int newX = ch->getX();
|
int newX = ch->getX();
|
||||||
int newWidth = ch->getWidth();
|
int newWidth = ch->getWidth();
|
||||||
if (anchorLeft && anchorRight)
|
if (anchorLeft && anchorRight)
|
||||||
{
|
{
|
||||||
// Scale horizontally relative to parent's size.
|
// Scale horizontally relative to parent's size.
|
||||||
if (origParentW > 0)
|
if (origParentW > 0)
|
||||||
{
|
{
|
||||||
// Maintain proportional position and size based on original local values.
|
// Maintain proportional position and size based on original local values.
|
||||||
double scaleW = static_cast<double>(finalW) / static_cast<double>(origParentW);
|
double scaleW = static_cast<double>(finalW) / static_cast<double>(origParentW);
|
||||||
newX = parentX + static_cast<int>(ch->getLocalX() * scaleW + 0.5);
|
newX = parentX + static_cast<int>(ch->getLocalX() * scaleW + 0.5);
|
||||||
newWidth = static_cast<int>(ch->getLocalWidth() * scaleW + 0.5);
|
newWidth = static_cast<int>(ch->getLocalWidth() * scaleW + 0.5);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Fallback: keep original
|
// Fallback: keep original
|
||||||
newX = parentX + ch->getLocalX();
|
newX = parentX + ch->getLocalX();
|
||||||
newWidth = ch->getLocalWidth();
|
newWidth = ch->getLocalWidth();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (anchorLeft && !anchorRight)
|
else if (anchorLeft && !anchorRight)
|
||||||
{
|
{
|
||||||
// Only left anchored: keep original width and left margin.
|
// Only left anchored: keep original width and left margin.
|
||||||
newWidth = ch->getLocalWidth();
|
newWidth = ch->getLocalWidth();
|
||||||
newX = parentX + ch->getLocalX();
|
newX = parentX + ch->getLocalX();
|
||||||
}
|
}
|
||||||
else if (!anchorLeft && anchorRight)
|
else if (!anchorLeft && anchorRight)
|
||||||
{
|
{
|
||||||
// Only right anchored: keep original width and right margin.
|
// Only right anchored: keep original width and right margin.
|
||||||
newWidth = ch->getLocalWidth();
|
newWidth = ch->getLocalWidth();
|
||||||
int origRightDist = origParentW - (ch->getLocalX() + ch->getLocalWidth());
|
int origRightDist = origParentW - (ch->getLocalX() + ch->getLocalWidth());
|
||||||
newX = parentX + finalW - origRightDist - newWidth;
|
newX = parentX + finalW - origRightDist - newWidth;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// No horizontal anchor: position relative to parent's left and width unchanged.
|
// No horizontal anchor: position relative to parent's left and width unchanged.
|
||||||
newWidth = ch->getLocalWidth();
|
newWidth = ch->getLocalWidth();
|
||||||
newX = parentX + ch->getLocalX();
|
newX = parentX + ch->getLocalX();
|
||||||
}
|
}
|
||||||
ch->setX(newX);
|
ch->setX(newX);
|
||||||
ch->setWidth(newWidth);
|
ch->setWidth(newWidth);
|
||||||
|
|
||||||
// Compute new Y and height
|
// Compute new Y and height
|
||||||
int newY = ch->getY();
|
int newY = ch->getY();
|
||||||
int newHeight = ch->getHeight();
|
int newHeight = ch->getHeight();
|
||||||
if (isTable)
|
if (isTable)
|
||||||
{
|
{
|
||||||
// Table: Height remains constant; adjust Y based on anchors.
|
// Table: Height remains constant; adjust Y based on anchors.
|
||||||
newHeight = ch->getLocalHeight();
|
newHeight = ch->getLocalHeight();
|
||||||
if (anchorTop && anchorBottom)
|
if (anchorTop && anchorBottom)
|
||||||
{
|
{
|
||||||
// If both top and bottom anchored, scale Y but keep height.
|
// If both top and bottom anchored, scale Y but keep height.
|
||||||
if (origParentH > 0)
|
if (origParentH > 0)
|
||||||
{
|
{
|
||||||
double scaleH = static_cast<double>(finalH) / static_cast<double>(origParentH);
|
double scaleH = static_cast<double>(finalH) / static_cast<double>(origParentH);
|
||||||
newY = parentY + static_cast<int>(ch->getLocalY() * scaleH + 0.5);
|
newY = parentY + static_cast<int>(ch->getLocalY() * scaleH + 0.5);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
newY = parentY + ch->getLocalY();
|
newY = parentY + ch->getLocalY();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (anchorTop && !anchorBottom)
|
else if (anchorTop && !anchorBottom)
|
||||||
{
|
{
|
||||||
// Top anchored only
|
// Top anchored only
|
||||||
newY = parentY + ch->getLocalY();
|
newY = parentY + ch->getLocalY();
|
||||||
}
|
}
|
||||||
else if (!anchorTop && anchorBottom)
|
else if (!anchorTop && anchorBottom)
|
||||||
{
|
{
|
||||||
// Bottom anchored only
|
// Bottom anchored only
|
||||||
int origBottomDist = origParentH - (ch->getLocalY() + ch->getLocalHeight());
|
int origBottomDist = origParentH - (ch->getLocalY() + ch->getLocalHeight());
|
||||||
newY = parentY + finalH - origBottomDist - newHeight;
|
newY = parentY + finalH - origBottomDist - newHeight;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// No vertical anchor: default to top
|
// No vertical anchor: default to top
|
||||||
newY = parentY + ch->getLocalY();
|
newY = parentY + ch->getLocalY();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (anchorTop && anchorBottom)
|
if (anchorTop && anchorBottom)
|
||||||
{
|
{
|
||||||
// Scale vertically relative to parent's size.
|
// Scale vertically relative to parent's size.
|
||||||
if (origParentH > 0)
|
if (origParentH > 0)
|
||||||
{
|
{
|
||||||
double scaleH = static_cast<double>(finalH) / static_cast<double>(origParentH);
|
double scaleH = static_cast<double>(finalH) / static_cast<double>(origParentH);
|
||||||
newY = parentY + static_cast<int>(ch->getLocalY() * scaleH + 0.5);
|
newY = parentY + static_cast<int>(ch->getLocalY() * scaleH + 0.5);
|
||||||
newHeight = static_cast<int>(ch->getLocalHeight() * scaleH + 0.5);
|
newHeight = static_cast<int>(ch->getLocalHeight() * scaleH + 0.5);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
newY = parentY + ch->getLocalY();
|
newY = parentY + ch->getLocalY();
|
||||||
newHeight = ch->getLocalHeight();
|
newHeight = ch->getLocalHeight();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (anchorTop && !anchorBottom)
|
else if (anchorTop && !anchorBottom)
|
||||||
{
|
{
|
||||||
// Top anchored only: keep height constant
|
// Top anchored only: keep height constant
|
||||||
newHeight = ch->getLocalHeight();
|
newHeight = ch->getLocalHeight();
|
||||||
newY = parentY + ch->getLocalY();
|
newY = parentY + ch->getLocalY();
|
||||||
}
|
}
|
||||||
else if (!anchorTop && anchorBottom)
|
else if (!anchorTop && anchorBottom)
|
||||||
{
|
{
|
||||||
// Bottom anchored only: keep height and adjust Y relative to bottom
|
// Bottom anchored only: keep height and adjust Y relative to bottom
|
||||||
newHeight = ch->getLocalHeight();
|
newHeight = ch->getLocalHeight();
|
||||||
int origBottomDist = origParentH - (ch->getLocalY() + ch->getLocalHeight());
|
int origBottomDist = origParentH - (ch->getLocalY() + ch->getLocalHeight());
|
||||||
newY = parentY + finalH - origBottomDist - newHeight;
|
newY = parentY + finalH - origBottomDist - newHeight;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// No vertical anchor: position relative to parent's top, height constant.
|
// No vertical anchor: position relative to parent's top, height constant.
|
||||||
newHeight = ch->getLocalHeight();
|
newHeight = ch->getLocalHeight();
|
||||||
newY = parentY + ch->getLocalY();
|
newY = parentY + ch->getLocalY();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ch->setY(newY);
|
ch->setY(newY);
|
||||||
ch->setHeight(newHeight);
|
ch->setHeight(newHeight);
|
||||||
}
|
}
|
||||||
// Always forward the window resize event to the child (recursively).
|
// Always forward the window resize event to the child (recursively).
|
||||||
ch->onWindowResize();
|
ch->onWindowResize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Canvas::requestRepaint(Control* parent)
|
void Canvas::requestRepaint(Control* parent)
|
||||||
{
|
{
|
||||||
if (this == parent)
|
if (this == parent)
|
||||||
{
|
{
|
||||||
|
if (!show)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 关键护栏:
|
||||||
|
// - Canvas 自己是脏的 / 没有快照 / 缓存图为空
|
||||||
|
// => 禁止局部重绘,直接升级为一次完整 draw(先把 dirty 置真,避免 draw() 早退)
|
||||||
|
if (dirty || !hasSnap || !saveBkImage)
|
||||||
|
{
|
||||||
|
SX_LOGD("Dirty")
|
||||||
|
<< SX_T("Canvas 局部重绘降级为全量重绘: id=", "Canvas partial->full draw: id=")
|
||||||
|
<< id
|
||||||
|
<< " dirty=" << (dirty ? 1 : 0)
|
||||||
|
<< " hasSnap=" << (hasSnap ? 1 : 0);
|
||||||
|
|
||||||
|
this->dirty = true;
|
||||||
|
this->draw();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SX_LOGD("Dirty") << SX_T("Canvas 请求局部重绘:id=", "Canvas::requestRepaint(partial): id=") << id;
|
||||||
|
|
||||||
for (auto& control : controls)
|
for (auto& control : controls)
|
||||||
if (control->isDirty() && control->IsVisible())
|
if (control->isDirty() && control->IsVisible())
|
||||||
control->draw();
|
control->draw();
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
onRequestRepaintAsRoot();
|
SX_LOGD("Dirty") << SX_T("Canvas 请求根级重绘:id=", "Canvas::requestRepaint(root): id=") << id;
|
||||||
|
onRequestRepaintAsRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "Control.h"
|
#include "Control.h"
|
||||||
|
#include "SxLog.h"
|
||||||
#include<assert.h>
|
#include<assert.h>
|
||||||
|
|
||||||
StellarX::ControlText& StellarX::ControlText::operator=(const ControlText& text)
|
StellarX::ControlText& StellarX::ControlText::operator=(const ControlText& text)
|
||||||
@@ -44,12 +45,34 @@ bool StellarX::ControlText::operator!=(const ControlText& text)
|
|||||||
}
|
}
|
||||||
void Control::setIsVisible(bool show)
|
void Control::setIsVisible(bool show)
|
||||||
{
|
{
|
||||||
if (!show)
|
SX_LOGD("Control") << SX_T("重置可见状态: id=", "setIsVisible: id=")
|
||||||
this->updateBackground();
|
<< id
|
||||||
|
<< " show=" << (show ? 1 : 0);
|
||||||
|
|
||||||
|
if (this->show == show)
|
||||||
|
return;
|
||||||
|
|
||||||
this->show = show;
|
this->show = show;
|
||||||
|
this->dirty = true;
|
||||||
|
|
||||||
|
if (!show)
|
||||||
|
{
|
||||||
|
// 隐藏:擦除自己在屏幕上的内容,并释放快照
|
||||||
|
this->updateBackground();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示:不在这里 requestRepaint(避免父容器快照未就绪时子控件抢跑 draw,污染快照)
|
||||||
|
// 仅向上标脏,让事件收口阶段由容器统一重绘。
|
||||||
|
if (parent)
|
||||||
|
parent->setDirty(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Control::onWindowResize()
|
void Control::onWindowResize()
|
||||||
{
|
{
|
||||||
|
SX_LOGD("Layout") << SX_T("尺寸变化:id=", "onWindowResize: id=") << id
|
||||||
|
<< SX_T(" -> 丢背景快照 + 标脏", " -> discardSnap + dirty");
|
||||||
|
|
||||||
// 自己:丢快照 + 标脏
|
// 自己:丢快照 + 标脏
|
||||||
discardBackground();
|
discardBackground();
|
||||||
setDirty(true);
|
setDirty(true);
|
||||||
@@ -100,12 +123,34 @@ void Control::restoreStyle()
|
|||||||
|
|
||||||
void Control::requestRepaint(Control* parent)
|
void Control::requestRepaint(Control* parent)
|
||||||
{
|
{
|
||||||
if (parent) parent->requestRepaint(parent); // 向上冒泡
|
// 说明:
|
||||||
else onRequestRepaintAsRoot(); // 到根控件/窗口兜底
|
// - 常规路径:子控件调用 requestRepaint(this->parent),然后 parent 负责局部重绘(Canvas/TabControl override)
|
||||||
|
// - 兜底路径:如果某个“容器控件”没 override requestRepaint,就会出现 parent==this 的递归风险
|
||||||
|
// 此时我们改为向更上层冒泡,直到根重绘。
|
||||||
|
if (parent == this)
|
||||||
|
{
|
||||||
|
SX_LOGW("Dirty")
|
||||||
|
<< SX_T("requestRepaint(默认容器兜底):id=", "requestRepaint(default-container-fallback): id=")
|
||||||
|
<< id
|
||||||
|
<< SX_T(",parent==this,向上层 parent 继续冒泡", " parent==this, bubble to upper parent");
|
||||||
|
|
||||||
|
if (this->parent) this->parent->requestRepaint(this->parent);
|
||||||
|
else onRequestRepaintAsRoot();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SX_LOGD("Dirty") << SX_T("请求重绘:id=","requestRepaint: id=") << id << " parent=" << (parent ? parent->getId() : "null");
|
||||||
|
|
||||||
|
if (parent) parent->requestRepaint(parent); // 交给容器处理(容器可局部重绘)
|
||||||
|
else onRequestRepaintAsRoot(); // 根兜底
|
||||||
}
|
}
|
||||||
|
|
||||||
void Control::onRequestRepaintAsRoot()
|
void Control::onRequestRepaintAsRoot()
|
||||||
{
|
{
|
||||||
|
SX_LOGI("Dirty")
|
||||||
|
<< SX_T("触发根重绘:id=", "onRequestRepaintAsRoot: id=") << id
|
||||||
|
<< SX_T("(从根节点开始重画)", " (root repaint)");
|
||||||
|
|
||||||
|
|
||||||
discardBackground();
|
discardBackground();
|
||||||
setDirty(true);
|
setDirty(true);
|
||||||
@@ -122,9 +167,13 @@ void Control::saveBackground(int x, int y, int w, int h)
|
|||||||
//尺寸变了才重建,避免反复 new/delete
|
//尺寸变了才重建,避免反复 new/delete
|
||||||
if (saveBkImage->getwidth() != w || saveBkImage->getheight() != h)
|
if (saveBkImage->getwidth() != w || saveBkImage->getheight() != h)
|
||||||
{
|
{
|
||||||
|
SX_LOGD("Snap") <<SX_T("重新保存背景快照:id=", "saveBackground rebuild: id=") << id << " size=(" << w << "x" << h << ")";
|
||||||
|
|
||||||
delete saveBkImage; saveBkImage = nullptr;
|
delete saveBkImage; saveBkImage = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
SX_LOGD("Snap") << SX_T("保存背景快照:id=", "saveBackground rebuild: id=") << id << " size=(" << w << "x" << h << ")";
|
||||||
if (!saveBkImage) saveBkImage = new IMAGE(w, h);
|
if (!saveBkImage) saveBkImage = new IMAGE(w, h);
|
||||||
|
|
||||||
SetWorkingImage(nullptr); // ★抓屏幕
|
SetWorkingImage(nullptr); // ★抓屏幕
|
||||||
@@ -145,6 +194,7 @@ void Control::discardBackground()
|
|||||||
if (saveBkImage)
|
if (saveBkImage)
|
||||||
{
|
{
|
||||||
restBackground();
|
restBackground();
|
||||||
|
SX_LOGD("Snap") << SX_T("丢弃背景快照:id=","discardBackground: id=") << id << " hasSnap=" << (hasSnap ? 1 : 0);
|
||||||
delete saveBkImage;
|
delete saveBkImage;
|
||||||
saveBkImage = nullptr;
|
saveBkImage = nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
117
src/Dialog.cpp
117
src/Dialog.cpp
@@ -1,21 +1,20 @@
|
|||||||
#include "Dialog.h"
|
#include "Dialog.h"
|
||||||
|
#include "SxLog.h"
|
||||||
|
|
||||||
Dialog::Dialog(Window& h,std::string text,std::string message, StellarX::MessageBoxType type, bool modal)
|
Dialog::Dialog(Window& h, std::string text, std::string message, StellarX::MessageBoxType type, bool modal)
|
||||||
: Canvas(),message(message), type(type), modal(modal), hWnd(h), titleText(text)
|
: Canvas(), message(message), type(type), modal(modal), hWnd(h), titleText(text)
|
||||||
{
|
{
|
||||||
this->id = "Dialog";
|
this->id = "Dialog";
|
||||||
show = false;
|
show = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Dialog::~Dialog()
|
Dialog::~Dialog()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void Dialog::draw()
|
void Dialog::draw()
|
||||||
{
|
{
|
||||||
if(!show)
|
if (!show)
|
||||||
{
|
{
|
||||||
// 如果对话框不可见且需要清理,执行清理
|
// 如果对话框不可见且需要清理,执行清理
|
||||||
if (pendingCleanup && !isCleaning)
|
if (pendingCleanup && !isCleaning)
|
||||||
@@ -24,16 +23,16 @@ void Dialog::draw()
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 如果需要初始化,则执行初始化
|
// 如果需要初始化,则执行初始化
|
||||||
if (needsInitialization && show)
|
if (needsInitialization && show)
|
||||||
{
|
{
|
||||||
initDialogSize();
|
initDialogSize();
|
||||||
needsInitialization = false;
|
needsInitialization = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dirty && show)
|
if (dirty && show)
|
||||||
{
|
{
|
||||||
// 保存当前绘图状态
|
// 保存当前绘图状态
|
||||||
saveStyle();
|
saveStyle();
|
||||||
|
|
||||||
Canvas::setBorderColor(this->borderColor);
|
Canvas::setBorderColor(this->borderColor);
|
||||||
@@ -51,24 +50,21 @@ void Dialog::draw()
|
|||||||
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 ty = y + closeButtonHeight + titleToTextMargin; // 文本起始Y坐标
|
int ty = y + closeButtonHeight + titleToTextMargin; // 文本起始Y坐标
|
||||||
for (auto& line:lines)
|
for (auto& line : lines)
|
||||||
{
|
{
|
||||||
int tx = this->x + ((this->width - textwidth(line.c_str())) / 2); // 文本起始X坐标
|
int tx = this->x + ((this->width - textwidth(line.c_str())) / 2); // 文本起始X坐标
|
||||||
outtextxy(tx, ty, LPCTSTR(line.c_str()));
|
outtextxy(tx, ty, LPCTSTR(line.c_str()));
|
||||||
ty = ty + textheight(LPCTSTR(line.c_str())) + 5; // 每行文本高度加5像素间距
|
ty = ty + textheight(LPCTSTR(line.c_str())) + 5; // 每行文本高度加5像素间距
|
||||||
}
|
}
|
||||||
|
|
||||||
// 恢复绘图状态
|
// 恢复绘图状态
|
||||||
restoreStyle();
|
restoreStyle();
|
||||||
|
|
||||||
dirty = false;
|
dirty = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool Dialog::handleEvent(const ExMessage& msg)
|
bool Dialog::handleEvent(const ExMessage& msg)
|
||||||
{
|
{
|
||||||
bool consume = false;
|
bool consume = false;
|
||||||
@@ -94,8 +90,8 @@ bool Dialog::handleEvent(const ExMessage& msg)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 将事件传递给子控件处理
|
// 将事件传递给子控件处理
|
||||||
if (!consume)
|
if (!consume)
|
||||||
consume = Canvas::handleEvent(msg);
|
consume = Canvas::handleEvent(msg);
|
||||||
|
|
||||||
// 每次事件处理后检查是否需要执行延迟清理
|
// 每次事件处理后检查是否需要执行延迟清理
|
||||||
if (pendingCleanup && !isCleaning)
|
if (pendingCleanup && !isCleaning)
|
||||||
@@ -134,7 +130,6 @@ void Dialog::SetModal(bool modal)
|
|||||||
this->modal = modal;
|
this->modal = modal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Dialog::SetResult(StellarX::MessageBoxResult result)
|
void Dialog::SetResult(StellarX::MessageBoxResult result)
|
||||||
{
|
{
|
||||||
this->result = result;
|
this->result = result;
|
||||||
@@ -154,6 +149,7 @@ void Dialog::Show()
|
|||||||
{
|
{
|
||||||
if (pendingCleanup)
|
if (pendingCleanup)
|
||||||
performDelayedCleanup();
|
performDelayedCleanup();
|
||||||
|
SX_LOGI("Dialog") << SX_T("对话框弹出:是否模态=","Dialog::Show: modal=") << (modal ? 1 : 0);
|
||||||
|
|
||||||
show = true;
|
show = true;
|
||||||
dirty = true;
|
dirty = true;
|
||||||
@@ -184,6 +180,7 @@ void Dialog::Show()
|
|||||||
{
|
{
|
||||||
lastW = cw;
|
lastW = cw;
|
||||||
lastH = ch;
|
lastH = ch;
|
||||||
|
SX_LOGD("Resize") <<SX_T("模态对话框检测到窗口大小变化:(", "Modal dialog detected window size change: (") << cw << "x" << ch << ")";
|
||||||
|
|
||||||
// 通知父窗口:有新尺寸 → 标记 needResizeDirty
|
// 通知父窗口:有新尺寸 → 标记 needResizeDirty
|
||||||
hWnd.scheduleResizeFromModal(cw, ch);
|
hWnd.scheduleResizeFromModal(cw, ch);
|
||||||
@@ -238,8 +235,6 @@ void Dialog::Show()
|
|||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void Dialog::Close()
|
void Dialog::Close()
|
||||||
{
|
{
|
||||||
if (!show) return;
|
if (!show) return;
|
||||||
@@ -249,12 +244,9 @@ void Dialog::Close()
|
|||||||
dirty = true;
|
dirty = true;
|
||||||
pendingCleanup = true; // 只标记需要清理,不立即执行
|
pendingCleanup = true; // 只标记需要清理,不立即执行
|
||||||
|
|
||||||
|
|
||||||
// 工厂模式下非模态触发回调 返回结果
|
// 工厂模式下非模态触发回调 返回结果
|
||||||
if (resultCallback&& !modal)
|
if (resultCallback && !modal)
|
||||||
resultCallback(this->result);
|
resultCallback(this->result);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Dialog::setInitialization(bool init)
|
void Dialog::setInitialization(bool init)
|
||||||
@@ -267,7 +259,6 @@ void Dialog::setInitialization(bool init)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Dialog::initButtons()
|
void Dialog::initButtons()
|
||||||
{
|
{
|
||||||
controls.clear();
|
controls.clear();
|
||||||
@@ -279,7 +270,7 @@ void Dialog::initButtons()
|
|||||||
auto okbutton = createDialogButton((this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
|
auto okbutton = createDialogButton((this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
|
||||||
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
|
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
|
||||||
"确定"
|
"确定"
|
||||||
);
|
);
|
||||||
okbutton->setOnClickListener([this]()
|
okbutton->setOnClickListener([this]()
|
||||||
{
|
{
|
||||||
this->SetResult(StellarX::MessageBoxResult::OK);
|
this->SetResult(StellarX::MessageBoxResult::OK);
|
||||||
@@ -289,30 +280,30 @@ void Dialog::initButtons()
|
|||||||
|
|
||||||
this->addControl(std::move(okbutton));
|
this->addControl(std::move(okbutton));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case StellarX::MessageBoxType::OKCancel: // 确定和取消按钮
|
case StellarX::MessageBoxType::OKCancel: // 确定和取消按钮
|
||||||
{
|
{
|
||||||
auto okButton = createDialogButton(
|
auto okButton = createDialogButton(
|
||||||
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
|
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
|
||||||
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
|
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
|
||||||
"确定"
|
"确定"
|
||||||
);
|
);
|
||||||
okButton->setOnClickListener([this]()
|
okButton->setOnClickListener([this]()
|
||||||
{
|
{
|
||||||
this->SetResult(StellarX::MessageBoxResult::OK);
|
this->SetResult(StellarX::MessageBoxResult::OK);
|
||||||
this->hWnd.dialogClose = true;
|
this->hWnd.dialogClose = true;
|
||||||
this->Close(); });
|
this->Close(); });
|
||||||
|
|
||||||
auto cancelButton = createDialogButton(
|
auto cancelButton = createDialogButton(
|
||||||
(okButton.get()->getX() + okButton.get()->getButtonWidth() + buttonMargin),
|
(okButton.get()->getX() + okButton.get()->getButtonWidth() + buttonMargin),
|
||||||
okButton.get()->getY(),
|
okButton.get()->getY(),
|
||||||
"取消"
|
"取消"
|
||||||
);
|
);
|
||||||
cancelButton->setOnClickListener([this]()
|
cancelButton->setOnClickListener([this]()
|
||||||
{
|
{
|
||||||
this->SetResult(StellarX::MessageBoxResult::Cancel);
|
this->SetResult(StellarX::MessageBoxResult::Cancel);
|
||||||
this->hWnd.dialogClose = true;
|
this->hWnd.dialogClose = true;
|
||||||
this->Close(); });
|
this->Close(); });
|
||||||
|
|
||||||
okButton->textStyle = this->textStyle;
|
okButton->textStyle = this->textStyle;
|
||||||
cancelButton->textStyle = this->textStyle;
|
cancelButton->textStyle = this->textStyle;
|
||||||
@@ -320,7 +311,7 @@ void Dialog::initButtons()
|
|||||||
this->addControl(std::move(okButton));
|
this->addControl(std::move(okButton));
|
||||||
this->addControl(std::move(cancelButton));
|
this->addControl(std::move(cancelButton));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case StellarX::MessageBoxType::YesNo: // 是和否按钮
|
case StellarX::MessageBoxType::YesNo: // 是和否按钮
|
||||||
{
|
{
|
||||||
auto yesButton = createDialogButton(
|
auto yesButton = createDialogButton(
|
||||||
@@ -332,7 +323,7 @@ void Dialog::initButtons()
|
|||||||
{
|
{
|
||||||
this->SetResult(StellarX::MessageBoxResult::Yes);
|
this->SetResult(StellarX::MessageBoxResult::Yes);
|
||||||
this->hWnd.dialogClose = true;
|
this->hWnd.dialogClose = true;
|
||||||
this->Close(); });
|
this->Close(); });
|
||||||
|
|
||||||
auto noButton = createDialogButton(
|
auto noButton = createDialogButton(
|
||||||
(yesButton.get()->getX() + yesButton.get()->getButtonWidth() + buttonMargin),
|
(yesButton.get()->getX() + yesButton.get()->getButtonWidth() + buttonMargin),
|
||||||
@@ -343,7 +334,7 @@ void Dialog::initButtons()
|
|||||||
{
|
{
|
||||||
this->SetResult(StellarX::MessageBoxResult::No);
|
this->SetResult(StellarX::MessageBoxResult::No);
|
||||||
this->hWnd.dialogClose = true;
|
this->hWnd.dialogClose = true;
|
||||||
this->Close(); });
|
this->Close(); });
|
||||||
|
|
||||||
yesButton->textStyle = this->textStyle;
|
yesButton->textStyle = this->textStyle;
|
||||||
noButton->textStyle = this->textStyle;
|
noButton->textStyle = this->textStyle;
|
||||||
@@ -351,7 +342,7 @@ void Dialog::initButtons()
|
|||||||
this->addControl(std::move(yesButton));
|
this->addControl(std::move(yesButton));
|
||||||
this->addControl(std::move(noButton));
|
this->addControl(std::move(noButton));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case StellarX::MessageBoxType::YesNoCancel: // 是、否和取消按钮
|
case StellarX::MessageBoxType::YesNoCancel: // 是、否和取消按钮
|
||||||
{
|
{
|
||||||
auto yesButton = createDialogButton(
|
auto yesButton = createDialogButton(
|
||||||
@@ -363,7 +354,7 @@ void Dialog::initButtons()
|
|||||||
{
|
{
|
||||||
this->SetResult(StellarX::MessageBoxResult::Yes);
|
this->SetResult(StellarX::MessageBoxResult::Yes);
|
||||||
this->hWnd.dialogClose = true;
|
this->hWnd.dialogClose = true;
|
||||||
this->Close(); });
|
this->Close(); });
|
||||||
|
|
||||||
auto noButton = createDialogButton(
|
auto noButton = createDialogButton(
|
||||||
yesButton.get()->getX() + yesButton.get()->getButtonWidth() + buttonMargin,
|
yesButton.get()->getX() + yesButton.get()->getButtonWidth() + buttonMargin,
|
||||||
@@ -374,7 +365,7 @@ void Dialog::initButtons()
|
|||||||
{
|
{
|
||||||
this->SetResult(StellarX::MessageBoxResult::No);
|
this->SetResult(StellarX::MessageBoxResult::No);
|
||||||
this->hWnd.dialogClose = true;
|
this->hWnd.dialogClose = true;
|
||||||
this->Close(); });
|
this->Close(); });
|
||||||
|
|
||||||
auto cancelButton = createDialogButton(
|
auto cancelButton = createDialogButton(
|
||||||
noButton.get()->getX() + noButton.get()->getButtonWidth() + buttonMargin,
|
noButton.get()->getX() + noButton.get()->getButtonWidth() + buttonMargin,
|
||||||
@@ -385,18 +376,17 @@ void Dialog::initButtons()
|
|||||||
{
|
{
|
||||||
this->SetResult(StellarX::MessageBoxResult::Cancel);
|
this->SetResult(StellarX::MessageBoxResult::Cancel);
|
||||||
this->hWnd.dialogClose = true;
|
this->hWnd.dialogClose = true;
|
||||||
this->Close(); });
|
this->Close(); });
|
||||||
|
|
||||||
yesButton->textStyle = this->textStyle;
|
yesButton->textStyle = this->textStyle;
|
||||||
noButton->textStyle = this->textStyle;
|
noButton->textStyle = this->textStyle;
|
||||||
cancelButton->textStyle = this->textStyle;
|
cancelButton->textStyle = this->textStyle;
|
||||||
|
|
||||||
|
|
||||||
this->addControl(std::move(yesButton));
|
this->addControl(std::move(yesButton));
|
||||||
this->addControl(std::move(noButton));
|
this->addControl(std::move(noButton));
|
||||||
this->addControl(std::move(cancelButton));
|
this->addControl(std::move(cancelButton));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case StellarX::MessageBoxType::RetryCancel: // 重试和取消按钮
|
case StellarX::MessageBoxType::RetryCancel: // 重试和取消按钮
|
||||||
{
|
{
|
||||||
auto retryButton = createDialogButton(
|
auto retryButton = createDialogButton(
|
||||||
@@ -408,7 +398,7 @@ void Dialog::initButtons()
|
|||||||
{
|
{
|
||||||
this->SetResult(StellarX::MessageBoxResult::Retry);
|
this->SetResult(StellarX::MessageBoxResult::Retry);
|
||||||
this->hWnd.dialogClose = true;
|
this->hWnd.dialogClose = true;
|
||||||
this->Close(); });
|
this->Close(); });
|
||||||
|
|
||||||
auto cancelButton = createDialogButton(
|
auto cancelButton = createDialogButton(
|
||||||
retryButton.get()->getX() + retryButton.get()->getButtonWidth() + buttonMargin,
|
retryButton.get()->getX() + retryButton.get()->getButtonWidth() + buttonMargin,
|
||||||
@@ -419,7 +409,7 @@ void Dialog::initButtons()
|
|||||||
{
|
{
|
||||||
this->SetResult(StellarX::MessageBoxResult::Cancel);
|
this->SetResult(StellarX::MessageBoxResult::Cancel);
|
||||||
this->hWnd.dialogClose = true;
|
this->hWnd.dialogClose = true;
|
||||||
this->Close(); });
|
this->Close(); });
|
||||||
|
|
||||||
retryButton->textStyle = this->textStyle;
|
retryButton->textStyle = this->textStyle;
|
||||||
cancelButton->textStyle = this->textStyle;
|
cancelButton->textStyle = this->textStyle;
|
||||||
@@ -427,11 +417,11 @@ void Dialog::initButtons()
|
|||||||
this->addControl(std::move(retryButton));
|
this->addControl(std::move(retryButton));
|
||||||
this->addControl(std::move(cancelButton));
|
this->addControl(std::move(cancelButton));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case StellarX::MessageBoxType::AbortRetryIgnore: // 中止、重试和忽略按钮
|
case StellarX::MessageBoxType::AbortRetryIgnore: // 中止、重试和忽略按钮
|
||||||
{
|
{
|
||||||
auto abortButton = createDialogButton(
|
auto abortButton = createDialogButton(
|
||||||
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin* (buttonNum-1))) / 2),
|
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
|
||||||
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
|
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
|
||||||
"中止"
|
"中止"
|
||||||
);
|
);
|
||||||
@@ -439,7 +429,7 @@ void Dialog::initButtons()
|
|||||||
{
|
{
|
||||||
this->SetResult(StellarX::MessageBoxResult::Abort);
|
this->SetResult(StellarX::MessageBoxResult::Abort);
|
||||||
this->hWnd.dialogClose = true;
|
this->hWnd.dialogClose = true;
|
||||||
this->Close();
|
this->Close();
|
||||||
});
|
});
|
||||||
auto retryButton = createDialogButton(
|
auto retryButton = createDialogButton(
|
||||||
abortButton.get()->getX() + abortButton.get()->getButtonWidth() + buttonMargin,
|
abortButton.get()->getX() + abortButton.get()->getButtonWidth() + buttonMargin,
|
||||||
@@ -450,7 +440,7 @@ void Dialog::initButtons()
|
|||||||
{
|
{
|
||||||
this->SetResult(StellarX::MessageBoxResult::Retry);
|
this->SetResult(StellarX::MessageBoxResult::Retry);
|
||||||
this->hWnd.dialogClose = true;
|
this->hWnd.dialogClose = true;
|
||||||
this->Close();
|
this->Close();
|
||||||
});
|
});
|
||||||
auto ignoreButton = createDialogButton(
|
auto ignoreButton = createDialogButton(
|
||||||
retryButton.get()->getX() + retryButton.get()->getButtonWidth() + buttonMargin,
|
retryButton.get()->getX() + retryButton.get()->getButtonWidth() + buttonMargin,
|
||||||
@@ -461,7 +451,7 @@ void Dialog::initButtons()
|
|||||||
{
|
{
|
||||||
this->SetResult(StellarX::MessageBoxResult::Ignore);
|
this->SetResult(StellarX::MessageBoxResult::Ignore);
|
||||||
this->hWnd.dialogClose = true;
|
this->hWnd.dialogClose = true;
|
||||||
this->Close();
|
this->Close();
|
||||||
});
|
});
|
||||||
|
|
||||||
abortButton->textStyle = this->textStyle;
|
abortButton->textStyle = this->textStyle;
|
||||||
@@ -472,7 +462,7 @@ void Dialog::initButtons()
|
|||||||
this->addControl(std::move(retryButton));
|
this->addControl(std::move(retryButton));
|
||||||
this->addControl(std::move(ignoreButton));
|
this->addControl(std::move(ignoreButton));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,7 +471,7 @@ void Dialog::initCloseButton()
|
|||||||
//初始化关闭按钮
|
//初始化关闭按钮
|
||||||
auto but = std::make_unique<Button>
|
auto but = std::make_unique<Button>
|
||||||
(
|
(
|
||||||
(this->x + this->width - closeButtonWidth) - 3, (this->y+3), closeButtonWidth-1, closeButtonHeight,
|
(this->x + this->width - closeButtonWidth) - 3, (this->y + 3), closeButtonWidth - 1, closeButtonHeight,
|
||||||
"X", // 按钮文本
|
"X", // 按钮文本
|
||||||
RGB(255, 0, 0), // 按钮被点击颜色
|
RGB(255, 0, 0), // 按钮被点击颜色
|
||||||
this->canvasBkClor, // 按钮背景颜色
|
this->canvasBkClor, // 按钮背景颜色
|
||||||
@@ -502,7 +492,7 @@ void Dialog::initCloseButton()
|
|||||||
|
|
||||||
void Dialog::initTitle()
|
void Dialog::initTitle()
|
||||||
{
|
{
|
||||||
this->title = std::make_unique<Label>(this->x+5,this->y+5,titleText,textStyle.color);
|
this->title = std::make_unique<Label>(this->x + 5, this->y + 5, titleText, textStyle.color);
|
||||||
title->setTextdisap(true);
|
title->setTextdisap(true);
|
||||||
title->textStyle = this->textStyle;
|
title->textStyle = this->textStyle;
|
||||||
|
|
||||||
@@ -516,7 +506,7 @@ void Dialog::splitMessageLines()
|
|||||||
std::string currentLine;
|
std::string currentLine;
|
||||||
for (size_t i = 0; i < message.length(); i++) {
|
for (size_t i = 0; i < message.length(); i++) {
|
||||||
// 处理 换行符 \r\n \n \r
|
// 处理 换行符 \r\n \n \r
|
||||||
if (i + 1 < message.length() && (message[i] == '\r' || message[i] == '\n')||(message[i] == '\r' && message[i+1] == '\n'))
|
if (i + 1 < message.length() && (message[i] == '\r' || message[i] == '\n') || (message[i] == '\r' && message[i + 1] == '\n'))
|
||||||
{
|
{
|
||||||
if (!currentLine.empty()) {
|
if (!currentLine.empty()) {
|
||||||
lines.push_back(currentLine);
|
lines.push_back(currentLine);
|
||||||
@@ -587,10 +577,10 @@ void Dialog::initDialogSize()
|
|||||||
|
|
||||||
// 计算按钮区域宽度
|
// 计算按钮区域宽度
|
||||||
int buttonAreaWidth = buttonNum * functionButtonWidth +
|
int buttonAreaWidth = buttonNum * functionButtonWidth +
|
||||||
(buttonNum > 0 ? (buttonNum +1) * buttonMargin : 0);
|
(buttonNum > 0 ? (buttonNum + 1) * buttonMargin : 0);
|
||||||
|
|
||||||
// 计算文本区域宽度(包括边距)
|
// 计算文本区域宽度(包括边距)
|
||||||
int textAreaWidth = textWidth + textToBorderMargin * 2 ;
|
int textAreaWidth = textWidth + textToBorderMargin * 2;
|
||||||
|
|
||||||
// 对话框宽度取两者中的较大值,并确保最小宽度
|
// 对话框宽度取两者中的较大值,并确保最小宽度
|
||||||
this->width = buttonAreaWidth > textAreaWidth ? buttonAreaWidth : textAreaWidth;
|
this->width = buttonAreaWidth > textAreaWidth ? buttonAreaWidth : textAreaWidth;
|
||||||
@@ -598,7 +588,7 @@ void Dialog::initDialogSize()
|
|||||||
|
|
||||||
// 计算对话框高度
|
// 计算对话框高度
|
||||||
// 高度 = 标题栏高度 + 文本区域高度 + 按钮区域高度 + 间距
|
// 高度 = 标题栏高度 + 文本区域高度 + 按钮区域高度 + 间距
|
||||||
int textAreaHeight = textHeight * (int)lines.size() + 5*((int)lines.size()-1); // 文本行高+行间距
|
int textAreaHeight = textHeight * (int)lines.size() + 5 * ((int)lines.size() - 1); // 文本行高+行间距
|
||||||
this->height = closeButtonHeight + // 标题栏高度
|
this->height = closeButtonHeight + // 标题栏高度
|
||||||
titleToTextMargin + // 标题到文本的间距
|
titleToTextMargin + // 标题到文本的间距
|
||||||
textAreaHeight + // 文本区域高度
|
textAreaHeight + // 文本区域高度
|
||||||
@@ -717,7 +707,6 @@ void Dialog::requestRepaint(Control* parent)
|
|||||||
for (auto& control : controls)
|
for (auto& control : controls)
|
||||||
if (control->isDirty() && control->IsVisible())
|
if (control->isDirty() && control->IsVisible())
|
||||||
control->draw();
|
control->draw();
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
onRequestRepaintAsRoot();
|
onRequestRepaintAsRoot();
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
#include "MessageBox.h"
|
#include "MessageBox.h"
|
||||||
|
#include "SxLog.h"
|
||||||
namespace StellarX
|
namespace StellarX
|
||||||
{
|
{
|
||||||
MessageBoxResult MessageBox::showModal(Window& wnd,const std::string& text,const std::string& caption,
|
MessageBoxResult MessageBox::showModal(Window& wnd, const std::string& text, const std::string& caption,
|
||||||
MessageBoxType type)
|
MessageBoxType type)
|
||||||
{
|
{
|
||||||
Dialog dlg(wnd, caption, text, type, true); // 模态
|
Dialog dlg(wnd, caption, text, type, true); // 模态
|
||||||
|
SX_LOGI("MessageBox") << "show: Message=" << dlg.GetText()
|
||||||
|
<< " modal=" << (dlg.model() ? 1 : 0);
|
||||||
|
|
||||||
dlg.setInitialization(true);
|
dlg.setInitialization(true);
|
||||||
dlg.Show();
|
dlg.Show();
|
||||||
return dlg.GetResult();
|
return dlg.GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageBox::showAsync(Window& wnd,const std::string& text,const std::string& caption,MessageBoxType type,
|
void MessageBox::showAsync(Window& wnd, const std::string& text, const std::string& caption, MessageBoxType type,
|
||||||
std::function<void(MessageBoxResult)> onResult)
|
std::function<void(MessageBoxResult)> onResult)
|
||||||
{
|
{
|
||||||
//去重,如果窗口内已有相同的对话框被触发,则不再创建
|
//去重,如果窗口内已有相同的对话框被触发,则不再创建
|
||||||
@@ -22,6 +25,8 @@ namespace StellarX
|
|||||||
}
|
}
|
||||||
auto dlg = std::make_unique<Dialog>(wnd, caption, text,
|
auto dlg = std::make_unique<Dialog>(wnd, caption, text,
|
||||||
type, false); // 非模态
|
type, false); // 非模态
|
||||||
|
SX_LOGI("MessageBox") << "show: Message=" << dlg->GetText()
|
||||||
|
<< " modal=" << (dlg->model() ? 1 : 0);
|
||||||
Dialog* dlgPtr = dlg.get();
|
Dialog* dlgPtr = dlg.get();
|
||||||
dlgPtr->setInitialization(true);
|
dlgPtr->setInitialization(true);
|
||||||
// 设置回调
|
// 设置回调
|
||||||
|
|||||||
446
src/SxLog.cpp
Normal file
446
src/SxLog.cpp
Normal file
@@ -0,0 +1,446 @@
|
|||||||
|
#include "SxLog.h"
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <clocale>
|
||||||
|
|
||||||
|
/********************************************************************************
|
||||||
|
* @文件: SxLog.cpp
|
||||||
|
* @摘要: StellarX 日志系统实现(过滤/格式化/输出/文件滚动/RAII提交/作用域计时)
|
||||||
|
* @描述:
|
||||||
|
* 该实现文件主要包含 4 个关键点:
|
||||||
|
* 1) FileSink: 文件打开、写入、flush 与按阈值滚动
|
||||||
|
* 2) SxLogger: shouldLog 过滤、formatPrefix 前缀拼接、logLine 统一输出出口
|
||||||
|
* 3) SxLogLine: 析构提交(RAII)确保“一条语句输出一整行”
|
||||||
|
* 4) SxLogScope: 按需启用计时,析构输出耗时
|
||||||
|
*
|
||||||
|
* @实现难点提示:
|
||||||
|
* - shouldLog 必须“零副作用”,否则宏短路会带来不可预测行为
|
||||||
|
* - logLine 是统一出口,必须保证行级一致性,且避免在持锁状态下递归打日志
|
||||||
|
* - 文件滚动要处理文件名安全性与跨平台 rename 行为差异
|
||||||
|
* - 时间戳生成需要兼容 Windows 与 POSIX(localtime_s/localtime_r)
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
namespace StellarX
|
||||||
|
{
|
||||||
|
// -------- FileSink --------
|
||||||
|
|
||||||
|
// 打开文件输出
|
||||||
|
// 难点:
|
||||||
|
// - 需要支持追加与清空两种模式
|
||||||
|
// - open 前先 close,避免重复打开导致句柄泄漏
|
||||||
|
bool FileSink::open(const std::string& path, bool append)
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
filePath = path;
|
||||||
|
appendMode = append;
|
||||||
|
|
||||||
|
std::ios::openmode mode = std::ios::out;
|
||||||
|
mode |= (append ? std::ios::app : std::ios::trunc);
|
||||||
|
|
||||||
|
ofs.open(path.c_str(), mode);
|
||||||
|
return ofs.is_open();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭文件输出(可重复调用)
|
||||||
|
void FileSink::close()
|
||||||
|
{
|
||||||
|
if (ofs.is_open()) ofs.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询是否已打开
|
||||||
|
bool FileSink::isOpen() const
|
||||||
|
{
|
||||||
|
return ofs.is_open();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入一整行
|
||||||
|
// 难点:
|
||||||
|
// - 写入后若启用 rotateBytes,需要及时检测文件大小是否到阈值
|
||||||
|
void FileSink::writeLine(const std::string& line)
|
||||||
|
{
|
||||||
|
if (!ofs.is_open()) return;
|
||||||
|
ofs << line;
|
||||||
|
if (rotateBytes > 0) rotateIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
// flush 文件缓冲
|
||||||
|
void FileSink::flush()
|
||||||
|
{
|
||||||
|
if (ofs.is_open()) ofs.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滚动文件
|
||||||
|
// 难点:
|
||||||
|
// 1) tellp() 返回的是当前写指针位置,通常可近似视为文件大小
|
||||||
|
// 2) 时间戳用于文件名时需要做字符清洗,避免出现不友好字符
|
||||||
|
// 3) rename 行为与权限/占用有关,失败时需要保证不崩溃(此处选择“尽力而为”)
|
||||||
|
bool FileSink::rotateIfNeeded()
|
||||||
|
{
|
||||||
|
if (!ofs.is_open() || rotateBytes == 0) return false;
|
||||||
|
|
||||||
|
const std::streampos pos = ofs.tellp();
|
||||||
|
if (pos < 0) return false;
|
||||||
|
|
||||||
|
const std::size_t size = static_cast<std::size_t>(pos);
|
||||||
|
if (size < rotateBytes) return false;
|
||||||
|
|
||||||
|
ofs.flush();
|
||||||
|
ofs.close();
|
||||||
|
|
||||||
|
// xxx.log -> xxx.log.YYYYmmdd_HHMMSS
|
||||||
|
// 说明:
|
||||||
|
// - makeTimestampLocal 形如 "2026-01-09 12:34:56"
|
||||||
|
// - 文件名中把 '-' ' ' ':' 替换为 '_',只保留数字与 '_',降低环境差异
|
||||||
|
const std::string ts = SxLogger::makeTimestampLocal();
|
||||||
|
std::string safeTs;
|
||||||
|
safeTs.reserve(ts.size());
|
||||||
|
for (char ch : ts)
|
||||||
|
{
|
||||||
|
if (ch >= '0' && ch <= '9') safeTs.push_back(ch);
|
||||||
|
else if (ch == '-' || ch == ' ' || ch == ':') safeTs.push_back('_');
|
||||||
|
}
|
||||||
|
if (safeTs.empty()) safeTs = "rotated";
|
||||||
|
|
||||||
|
const std::string rotated = filePath + "." + safeTs;
|
||||||
|
std::rename(filePath.c_str(), rotated.c_str());
|
||||||
|
|
||||||
|
// 重新打开新文件
|
||||||
|
// 注意: 这里用 append=false,确保新文件从空开始
|
||||||
|
return open(filePath, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- SxLogger --------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 设置 Windows 控制台 codepage(只执行一次)
|
||||||
|
// 难点:
|
||||||
|
// - 只影响终端解释输出字节的方式,不影响源码文件编码
|
||||||
|
// - 使用 once_flag 避免重复 system 调用造成噪声与性能浪费
|
||||||
|
//
|
||||||
|
// 注意:
|
||||||
|
// - 下面原注释写“切到 UTF-8”,但实际命令是 chcp 936(GBK)
|
||||||
|
// - 为避免改动你原注释,这里补充说明事实,保持行为不变
|
||||||
|
void SxLogger::setGBK()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
static std::once_flag once;
|
||||||
|
std::call_once(once, []() {
|
||||||
|
// 切到 UTF-8,避免中文日志在 CP936 控制台下乱码
|
||||||
|
// 说明:这不是 WinAPI;是执行系统命令
|
||||||
|
std::system("chcp 936 >nul");
|
||||||
|
|
||||||
|
// 补充说明:
|
||||||
|
// - chcp 936 实际是设置为 CP936(GBK)
|
||||||
|
// - 如果你的终端本身是 UTF-8 环境,调用它可能反而改变显示行为
|
||||||
|
// - 该函数建议只在“明确需要 GBK 控制台输出”的场景调用
|
||||||
|
|
||||||
|
// 尝试让 C/C++ 运行库按 UTF-8 工作(对部分流输出有帮助)
|
||||||
|
// std::setlocale(LC_ALL, ".UTF8");
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取单例
|
||||||
|
// 难点:
|
||||||
|
// - 作为全局入口,初始化必须线程安全
|
||||||
|
// - C++11 起函数内静态对象初始化由标准保证线程安全
|
||||||
|
SxLogger& SxLogger::Get()
|
||||||
|
{
|
||||||
|
static SxLogger inst;
|
||||||
|
return inst;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造:设置默认语言
|
||||||
|
SxLogger::SxLogger()
|
||||||
|
: lang(SxLogLanguage::ZhCN)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置最低输出级别
|
||||||
|
void SxLogger::setMinLevel(SxLogLevel level)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
cfg.minLevel = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取最低输出级别
|
||||||
|
SxLogLevel SxLogger::getMinLevel() const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
return cfg.minLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置语言
|
||||||
|
// 难点:
|
||||||
|
// - 语言只影响 SX_T 的字符串选择
|
||||||
|
// - 这里用 atomic relaxed,避免频繁加锁
|
||||||
|
void SxLogger::setLanguage(SxLogLanguage l)
|
||||||
|
{
|
||||||
|
lang.store(l, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取语言
|
||||||
|
SxLogLanguage SxLogger::getLanguage() const
|
||||||
|
{
|
||||||
|
return lang.load(std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置 Tag 过滤
|
||||||
|
// 难点:
|
||||||
|
// - 当前实现是 vector<string> 线性匹配,适合 tag 数量不大
|
||||||
|
// - 若未来 tag 很多,可考虑 unordered_set 优化(但会增加依赖与复杂度)
|
||||||
|
void SxLogger::setTagFilter(SxTagFilterMode mode, const std::vector<std::string>& tags)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
cfg.tagFilterMode = mode;
|
||||||
|
cfg.tagList = tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空 Tag 过滤
|
||||||
|
void SxLogger::clearTagFilter()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
cfg.tagFilterMode = SxTagFilterMode::None;
|
||||||
|
cfg.tagList.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开关控制台输出
|
||||||
|
// 难点:
|
||||||
|
// - ConsoleSink 持有 ostream 引用,不管理其生命周期
|
||||||
|
void SxLogger::enableConsole(bool enable)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
if (enable)
|
||||||
|
{
|
||||||
|
if (!consoleSink) consoleSink.reset(new ConsoleSink(std::cout));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
consoleSink.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开启文件输出
|
||||||
|
// 难点:
|
||||||
|
// - enableFile 成功与否决定 cfg.fileEnabled
|
||||||
|
// - 需要把 rotateBytes 同步到 FileSink
|
||||||
|
bool SxLogger::enableFile(const std::string& path, bool append, std::size_t rotateBytes_)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
|
||||||
|
if (!fileSink) fileSink.reset(new FileSink());
|
||||||
|
fileSink->setRotateBytes(rotateBytes_);
|
||||||
|
|
||||||
|
const bool ok = fileSink->open(path, append);
|
||||||
|
cfg.fileEnabled = ok;
|
||||||
|
cfg.filePath = path;
|
||||||
|
cfg.fileAppend = append;
|
||||||
|
cfg.rotateBytes = rotateBytes_;
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭文件输出
|
||||||
|
void SxLogger::disableFile()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
if (fileSink) fileSink->close();
|
||||||
|
cfg.fileEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取配置副本
|
||||||
|
// 难点:
|
||||||
|
// - 返回副本避免外部拿到内部引用后绕过锁修改
|
||||||
|
SxLogConfig SxLogger::getConfigCopy() const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置配置(整体替换)
|
||||||
|
void SxLogger::setConfig(const SxLogConfig& c)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
cfg = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 级别转字符串
|
||||||
|
const char* SxLogger::levelToString(SxLogLevel level)
|
||||||
|
{
|
||||||
|
switch (level)
|
||||||
|
{
|
||||||
|
case SxLogLevel::Trace: return "TRACE";
|
||||||
|
case SxLogLevel::Debug: return "DEBUG";
|
||||||
|
case SxLogLevel::Info: return "INFO ";
|
||||||
|
case SxLogLevel::Warn: return "WARN ";
|
||||||
|
case SxLogLevel::Error: return "ERROR";
|
||||||
|
case SxLogLevel::Fatal: return "FATAL";
|
||||||
|
default: return "OFF ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断 tag 是否允许输出
|
||||||
|
// 难点:
|
||||||
|
// - 精确匹配 tag 字符串
|
||||||
|
// - tag==nullptr 时默认允许,避免“无 tag 日志被误杀”
|
||||||
|
bool SxLogger::tagAllowed(const SxLogConfig& c, const char* tag)
|
||||||
|
{
|
||||||
|
if (c.tagFilterMode == SxTagFilterMode::None) return true;
|
||||||
|
if (!tag) return true;
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
for (const auto& t : c.tagList)
|
||||||
|
{
|
||||||
|
if (t == tag) { found = true; break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.tagFilterMode == SxTagFilterMode::Whitelist) return found;
|
||||||
|
if (c.tagFilterMode == SxTagFilterMode::Blacklist) return !found;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 快速判定是否需要输出(宏短路依赖)
|
||||||
|
// 难点:
|
||||||
|
// 1) 必须无副作用:返回 false 时调用端不会构造对象也不会拼接
|
||||||
|
// 2) 过滤维度要完整:级别、tag、sink 是否启用
|
||||||
|
// 3) 当前实现加锁保证 cfg 与 sink 状态一致;代价是高频路径会有锁开销
|
||||||
|
bool SxLogger::shouldLog(SxLogLevel level, const char* tag) const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
if (cfg.minLevel == SxLogLevel::Off) return false;
|
||||||
|
if (level < cfg.minLevel) return false;
|
||||||
|
if (!tagAllowed(cfg, tag)) return false;
|
||||||
|
if (!consoleSink && !cfg.fileEnabled) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成本地时间戳字符串
|
||||||
|
// 难点:
|
||||||
|
// - Windows 与 POSIX 的线程安全 localtime API 不同
|
||||||
|
std::string SxLogger::makeTimestampLocal()
|
||||||
|
{
|
||||||
|
using namespace std::chrono;
|
||||||
|
const auto now = system_clock::now();
|
||||||
|
const std::time_t t = system_clock::to_time_t(now);
|
||||||
|
|
||||||
|
std::tm tmv{};
|
||||||
|
#if defined(_WIN32)
|
||||||
|
localtime_s(&tmv, &t);
|
||||||
|
#else
|
||||||
|
localtime_r(&t, &tmv);
|
||||||
|
#endif
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << std::put_time(&tmv, "%Y-%m-%d %H:%M:%S");
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拼接日志前缀(调用方已持锁)
|
||||||
|
// 难点:
|
||||||
|
// - 前缀拼接必须与配置项严格对应,且尽量避免多余开销
|
||||||
|
// - showSource 会输出 (file:line func),对定位时序问题很有价值
|
||||||
|
std::string SxLogger::formatPrefixUnlocked(
|
||||||
|
const SxLogConfig& c,
|
||||||
|
SxLogLevel level,
|
||||||
|
const char* tag,
|
||||||
|
const char* file,
|
||||||
|
int line,
|
||||||
|
const char* func) const
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
|
||||||
|
if (c.showTimestamp) oss << "[" << makeTimestampLocal() << "] ";
|
||||||
|
if (c.showLevel) oss << "[" << levelToString(level) << "] ";
|
||||||
|
if (c.showTag && tag) oss << "[" << tag << "] ";
|
||||||
|
|
||||||
|
if (c.showThreadId)
|
||||||
|
{
|
||||||
|
oss << "[T:" << std::this_thread::get_id() << "] ";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.showSource && file && func)
|
||||||
|
{
|
||||||
|
oss << "(" << file << ":" << line << " " << func << ") ";
|
||||||
|
}
|
||||||
|
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统一输出出口
|
||||||
|
// 难点:
|
||||||
|
// 1) 行级一致性:必须把 prefix + msg + "\n" 当作整体写入
|
||||||
|
// 2) 线程安全:持锁写入可避免不同线程日志互相穿插
|
||||||
|
// 3) 避免重入:在持锁期间不要再调用 SX_LOG...(会导致死锁)
|
||||||
|
void SxLogger::logLine(
|
||||||
|
SxLogLevel level,
|
||||||
|
const char* tag,
|
||||||
|
const char* file,
|
||||||
|
int line,
|
||||||
|
const char* func,
|
||||||
|
const std::string& msg)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
|
||||||
|
if (cfg.minLevel == SxLogLevel::Off) return;
|
||||||
|
if (level < cfg.minLevel) return;
|
||||||
|
if (!tagAllowed(cfg, tag)) return;
|
||||||
|
|
||||||
|
const std::string prefix = formatPrefixUnlocked(cfg, level, tag, file, line, func);
|
||||||
|
const std::string lineText = prefix + msg + "\n";
|
||||||
|
|
||||||
|
if (consoleSink) consoleSink->writeLine(lineText);
|
||||||
|
|
||||||
|
if (cfg.fileEnabled && fileSink && fileSink->isOpen())
|
||||||
|
{
|
||||||
|
fileSink->writeLine(lineText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cfg.autoFlush)
|
||||||
|
{
|
||||||
|
if (consoleSink) consoleSink->flush();
|
||||||
|
if (cfg.fileEnabled && fileSink) fileSink->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- SxLogLine --------
|
||||||
|
|
||||||
|
// 构造:只记录元信息
|
||||||
|
SxLogLine::SxLogLine(SxLogLevel level, const char* tag, const char* file, int line, const char* func)
|
||||||
|
: lvl(level), tg(tag), srcFile(file), srcLine(line), srcFunc(func)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// 析构:提交输出
|
||||||
|
// 难点:
|
||||||
|
// - 这是 RAII 设计的核心:保证语句结束时日志自动落地
|
||||||
|
// - 也要求调用端不要把临时对象跨语句保存(宏用法本身也不支持那样做)
|
||||||
|
SxLogLine::~SxLogLine()
|
||||||
|
{
|
||||||
|
SxLogger::Get().logLine(lvl, tg, srcFile, srcLine, srcFunc, ss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- SxLogScope --------
|
||||||
|
|
||||||
|
// 构造:按需启用计时
|
||||||
|
// 难点:
|
||||||
|
// - 只有 shouldLog 为 true 才记录起点,避免在未输出场景做无意义计时
|
||||||
|
SxLogScope::SxLogScope(SxLogLevel level, const char* tag, const char* file, int line, const char* func, const char* name)
|
||||||
|
: lvl(level), tg(tag), srcFile(file), srcLine(line), srcFunc(func), scopeName(name)
|
||||||
|
{
|
||||||
|
enabled = SxLogger::Get().shouldLog(lvl, tg);
|
||||||
|
if (enabled) t0 = std::chrono::steady_clock::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 析构:输出耗时
|
||||||
|
// 难点:
|
||||||
|
// - steady_clock 用于衡量耗时,避免系统时间调整造成跳变
|
||||||
|
SxLogScope::~SxLogScope()
|
||||||
|
{
|
||||||
|
if (!enabled) return;
|
||||||
|
const auto t1 = std::chrono::steady_clock::now();
|
||||||
|
const auto us = std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count();
|
||||||
|
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "SCOPE " << (scopeName ? scopeName : "") << " cost=" << us << "us";
|
||||||
|
SxLogger::Get().logLine(lvl, tg, srcFile, srcLine, srcFunc, oss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace StellarX
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "TabControl.h"
|
#include "TabControl.h"
|
||||||
|
#include "SxLog.h"
|
||||||
inline void TabControl::initTabBar()
|
inline void TabControl::initTabBar()
|
||||||
{
|
{
|
||||||
if (controls.empty())return;
|
if (controls.empty())return;
|
||||||
@@ -33,7 +33,7 @@ inline void TabControl::initTabBar()
|
|||||||
for (auto& c : controls)
|
for (auto& c : controls)
|
||||||
{
|
{
|
||||||
c.first->setX(this->x + i * butW);
|
c.first->setX(this->x + i * butW);
|
||||||
c.first->setY(this->y+this->height - tabBarHeight);
|
c.first->setY(this->y + this->height - tabBarHeight);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -41,18 +41,18 @@ inline void TabControl::initTabBar()
|
|||||||
for (auto& c : controls)
|
for (auto& c : controls)
|
||||||
{
|
{
|
||||||
c.first->setX(this->x);
|
c.first->setX(this->x);
|
||||||
c.first->setY(this->y+i* butH);
|
c.first->setY(this->y + i * butH);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case StellarX::TabPlacement::Right:
|
case StellarX::TabPlacement::Right:
|
||||||
for (auto& c : controls)
|
for (auto& c : controls)
|
||||||
{
|
{
|
||||||
c.first->setX(this->x+this->width - tabBarHeight);
|
c.first->setX(this->x + this->width - tabBarHeight);
|
||||||
c.first->setY(this->y + i * butH);
|
c.first->setY(this->y + i * butH);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -147,7 +147,7 @@ inline void TabControl::initTabPage()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TabControl::TabControl():Canvas()
|
TabControl::TabControl() :Canvas()
|
||||||
{
|
{
|
||||||
this->id = "TabControl";
|
this->id = "TabControl";
|
||||||
}
|
}
|
||||||
@@ -173,7 +173,6 @@ void TabControl::setX(int x)
|
|||||||
c.first->onWindowResize();
|
c.first->onWindowResize();
|
||||||
c.second->onWindowResize();
|
c.second->onWindowResize();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabControl::setY(int y)
|
void TabControl::setY(int y)
|
||||||
@@ -192,28 +191,8 @@ void TabControl::setY(int y)
|
|||||||
void TabControl::draw()
|
void TabControl::draw()
|
||||||
{
|
{
|
||||||
if (!dirty || !show)return;
|
if (!dirty || !show)return;
|
||||||
// // 在绘制 TabControl 之前,先恢复并更新背景快照:
|
// 绘制画布背景和基本形状及其子画布控件
|
||||||
//int margin = canvaslinewidth > 1 ? canvaslinewidth : 1;
|
Canvas::draw();
|
||||||
//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)
|
for (auto& c : controls)
|
||||||
{
|
{
|
||||||
c.first->setDirty(true);
|
c.first->setDirty(true);
|
||||||
@@ -224,8 +203,17 @@ void TabControl::draw()
|
|||||||
c.second->setDirty(true);
|
c.second->setDirty(true);
|
||||||
c.second->draw();
|
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)
|
bool TabControl::handleEvent(const ExMessage& msg)
|
||||||
@@ -239,7 +227,7 @@ bool TabControl::handleEvent(const ExMessage& msg)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
for (auto& c : controls)
|
for (auto& c : controls)
|
||||||
if(c.second->IsVisible())
|
if (c.second->IsVisible())
|
||||||
if (c.second->handleEvent(msg))
|
if (c.second->handleEvent(msg))
|
||||||
{
|
{
|
||||||
consume = true;
|
consume = true;
|
||||||
@@ -260,24 +248,38 @@ void TabControl::add(std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>
|
|||||||
controls[idx].first->enableTooltip(true);
|
controls[idx].first->enableTooltip(true);
|
||||||
controls[idx].first->setbuttonMode(StellarX::ButtonMode::TOGGLE);
|
controls[idx].first->setbuttonMode(StellarX::ButtonMode::TOGGLE);
|
||||||
|
|
||||||
controls[idx].first->setOnToggleOnListener([this,idx]()
|
controls[idx].first->setOnToggleOnListener([this, idx]()
|
||||||
{
|
{
|
||||||
controls[idx].second->setIsVisible(true);
|
int prevIdx = -1;
|
||||||
controls[idx].second->onWindowResize();
|
for (size_t i = 0; i < controls.size(); ++i)
|
||||||
for (auto& tab : controls)
|
|
||||||
{
|
{
|
||||||
if (tab.first->getButtonText() != controls[idx].first->getButtonText())
|
if (controls[i].second->IsVisible())
|
||||||
{
|
{
|
||||||
tab.first->setButtonClick(false);
|
prevIdx = (int)i;
|
||||||
tab.second->setIsVisible(false);
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dirty = true;
|
for (auto& tab : controls)
|
||||||
});
|
{
|
||||||
controls[idx].first->setOnToggleOffListener([this,idx]()
|
if (tab.first->getButtonText() != controls[idx].first->getButtonText() && tab.first->isClicked())
|
||||||
{
|
tab.first->setButtonClick(false);
|
||||||
controls[idx].second->setIsVisible(false);
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SX_LOGI("Tab") << SX_T("激活选项卡:","activate tab: ") << prevIdx << "->" << (int)idx
|
||||||
|
<< " text=" << controls[idx].first->getButtonText();
|
||||||
|
controls[idx].second->onWindowResize();
|
||||||
|
controls[idx].second->setIsVisible(true);
|
||||||
|
dirty = true;
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
controls[idx].first->setOnToggleOffListener([this, idx]()
|
||||||
|
{
|
||||||
|
SX_LOGI("Tab") << SX_T("关闭选项卡:id=","deactivate tab: idx=") << (int)idx
|
||||||
|
<< " text=" << controls[idx].first->getButtonText();
|
||||||
|
|
||||||
|
controls[idx].second->setIsVisible(false);
|
||||||
dirty = true;
|
dirty = true;
|
||||||
});
|
});
|
||||||
controls[idx].second->setParent(this);
|
controls[idx].second->setParent(this);
|
||||||
@@ -293,12 +295,11 @@ void TabControl::add(std::string tabText, std::unique_ptr<Control> control)
|
|||||||
if (tab.first->getButtonText() == tabText)
|
if (tab.first->getButtonText() == tabText)
|
||||||
{
|
{
|
||||||
control->setParent(tab.second.get());
|
control->setParent(tab.second.get());
|
||||||
control->setIsVisible( tab.second->IsVisible());
|
control->setIsVisible(tab.second->IsVisible());
|
||||||
tab.second->addControl(std::move(control));
|
tab.second->addControl(std::move(control));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabControl::setTabPlacement(StellarX::TabPlacement placement)
|
void TabControl::setTabPlacement(StellarX::TabPlacement placement)
|
||||||
@@ -307,7 +308,6 @@ void TabControl::setTabPlacement(StellarX::TabPlacement placement)
|
|||||||
setDirty(true);
|
setDirty(true);
|
||||||
initTabBar();
|
initTabBar();
|
||||||
initTabPage();
|
initTabPage();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabControl::setTabBarHeight(int height)
|
void TabControl::setTabBarHeight(int height)
|
||||||
@@ -321,33 +321,42 @@ void TabControl::setTabBarHeight(int height)
|
|||||||
void TabControl::setIsVisible(bool visible)
|
void TabControl::setIsVisible(bool visible)
|
||||||
{
|
{
|
||||||
// 先让基类 Canvas 处理自己的回贴/丢快照逻辑
|
// 先让基类 Canvas 处理自己的回贴/丢快照逻辑
|
||||||
Canvas::setIsVisible(visible); // <--- 新增
|
Canvas::setIsVisible(visible);
|
||||||
|
|
||||||
this->show = visible;
|
|
||||||
for (auto& tab : controls)
|
for (auto& tab : controls)
|
||||||
{
|
{
|
||||||
tab.first->setIsVisible(visible);
|
if(true == visible)
|
||||||
//页也要跟着关/开,否则它们会保留旧的 saveBkImage
|
{
|
||||||
tab.second->setIsVisible(visible);
|
tab.first->setIsVisible(visible);
|
||||||
tab.second->setDirty(true);
|
//页也要跟着关/开,否则它们会保留旧的 saveBkImage
|
||||||
|
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()
|
void TabControl::onWindowResize()
|
||||||
{
|
{
|
||||||
// 调用基类的窗口变化处理,丢弃快照并标记脏
|
// 调用基类的窗口变化处理,丢弃快照并标记脏
|
||||||
Control::onWindowResize();
|
Control::onWindowResize();
|
||||||
// 根据当前 TabControl 的新尺寸重新计算页签栏和页面区域
|
// 根据当前 TabControl 的新尺寸重新计算页签栏和页面区域
|
||||||
initTabBar();
|
initTabBar();
|
||||||
initTabPage();
|
initTabPage();
|
||||||
// 转发窗口尺寸变化给所有页签按钮和页面
|
// 转发窗口尺寸变化给所有页签按钮和页面
|
||||||
for (auto& c : controls)
|
for (auto& c : controls)
|
||||||
{
|
{
|
||||||
c.first->onWindowResize();
|
c.first->onWindowResize();
|
||||||
c.second->onWindowResize();
|
c.second->onWindowResize();
|
||||||
}
|
}
|
||||||
// 尺寸变化后需要重绘自身
|
// 尺寸变化后需要重绘自身
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int TabControl::getActiveIndex() const
|
int TabControl::getActiveIndex() const
|
||||||
@@ -364,20 +373,13 @@ int TabControl::getActiveIndex() const
|
|||||||
|
|
||||||
void TabControl::setActiveIndex(int idx)
|
void TabControl::setActiveIndex(int idx)
|
||||||
{
|
{
|
||||||
if (idx < 0 || idx > controls.size() - 1) return;
|
if (IsFirstDraw)
|
||||||
if (controls[idx].first->getButtonMode() == StellarX::ButtonMode::DISABLED)return;
|
defaultActivation = idx;
|
||||||
if (controls[idx].first->isClicked())
|
|
||||||
{
|
|
||||||
if (controls[idx].second->IsVisible())
|
|
||||||
return;
|
|
||||||
else
|
|
||||||
controls[idx].second->setIsVisible(true);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
controls[idx].first->setButtonClick(true);
|
if (idx >= 0 && idx < controls.size())
|
||||||
|
controls[idx].first->setButtonClick(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int TabControl::count() const
|
int TabControl::count() const
|
||||||
@@ -388,7 +390,7 @@ int TabControl::count() const
|
|||||||
int TabControl::indexOf(const std::string& tabText) const
|
int TabControl::indexOf(const std::string& tabText) const
|
||||||
{
|
{
|
||||||
int idx = -1;
|
int idx = -1;
|
||||||
for(auto& c : controls)
|
for (auto& c : controls)
|
||||||
{
|
{
|
||||||
idx++;
|
idx++;
|
||||||
if (c.first->getButtonText() == tabText)
|
if (c.first->getButtonText() == tabText)
|
||||||
@@ -416,7 +418,7 @@ void TabControl::requestRepaint(Control* parent)
|
|||||||
{
|
{
|
||||||
if (control.first->isDirty() && control.first->IsVisible())
|
if (control.first->isDirty() && control.first->IsVisible())
|
||||||
control.first->draw();
|
control.first->draw();
|
||||||
else if (control.second->isDirty()&&control.second->IsVisible())
|
if (control.second->isDirty() && control.second->IsVisible())
|
||||||
control.second->draw();
|
control.second->draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Label::Label()
|
|||||||
{
|
{
|
||||||
this->id = "Label";
|
this->id = "Label";
|
||||||
this->text = "默认标签";
|
this->text = "默认标签";
|
||||||
textStyle.color = RGB(0,0,0);
|
textStyle.color = RGB(0, 0, 0);
|
||||||
textBkColor = RGB(255, 255, 255);; //默认白色背景
|
textBkColor = RGB(255, 255, 255);; //默认白色背景
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ void Label::draw()
|
|||||||
this->height = textheight(text.c_str());
|
this->height = textheight(text.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);
|
||||||
// 恢复背景(清除旧内容)
|
// 恢复背景(清除旧内容)
|
||||||
restBackground();
|
restBackground();
|
||||||
outtextxy(x, y, LPCTSTR(text.c_str()));
|
outtextxy(x, y, LPCTSTR(text.c_str()));
|
||||||
@@ -71,5 +71,4 @@ void Label::setText(std::string text)
|
|||||||
{
|
{
|
||||||
this->text = text;
|
this->text = text;
|
||||||
this->dirty = true;
|
this->dirty = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
280
src/table.cpp
280
src/table.cpp
@@ -1,4 +1,5 @@
|
|||||||
#include "Table.h"
|
#include "Table.h"
|
||||||
|
#include "SxLog.h"
|
||||||
// 绘制表格的当前页
|
// 绘制表格的当前页
|
||||||
// 使用双循环绘制行和列,考虑分页偏移
|
// 使用双循环绘制行和列,考虑分页偏移
|
||||||
void Table::drawTable()
|
void Table::drawTable()
|
||||||
@@ -26,12 +27,10 @@ void Table::drawTable()
|
|||||||
dY = uY;
|
dY = uY;
|
||||||
uY = dY + lineHeights.at(0) + TABLE_ROW_EXTRA;
|
uY = dY + lineHeights.at(0) + TABLE_ROW_EXTRA;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::drawHeader()
|
void Table::drawHeader()
|
||||||
{
|
{
|
||||||
|
|
||||||
const int border = tableBorderWidth > 0 ? tableBorderWidth : 0;
|
const int border = tableBorderWidth > 0 ? tableBorderWidth : 0;
|
||||||
// 内容区原点 = x+border, y+border
|
// 内容区原点 = x+border, y+border
|
||||||
dX = x + border;
|
dX = x + border;
|
||||||
@@ -87,35 +86,35 @@ void Table::initTextWaH()
|
|||||||
if (h > maxLineH)
|
if (h > maxLineH)
|
||||||
maxLineH = h;
|
maxLineH = h;
|
||||||
|
|
||||||
// 列宽包含左右 padding:在计算完最大文本宽度后,加上 2*padX 作为单元格内边距
|
// 列宽包含左右 padding:在计算完最大文本宽度后,加上 2*padX 作为单元格内边距
|
||||||
for (size_t j = 0; j < colWidths.size(); ++j) {
|
for (size_t j = 0; j < colWidths.size(); ++j) {
|
||||||
colWidths[j] += 2 * padX;
|
colWidths[j] += 2 * padX;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表内容总宽 = Σ(列宽 + 列间距)
|
// 表内容总宽 = Σ(列宽 + 列间距)
|
||||||
int contentW = 0;
|
int contentW = 0;
|
||||||
for (size_t j = 0; j < colWidths.size(); ++j)
|
for (size_t j = 0; j < colWidths.size(); ++j)
|
||||||
contentW += colWidths[j] + colGap;
|
contentW += colWidths[j] + colGap;
|
||||||
|
|
||||||
// 表头高 & 行高(与 drawHeader/drawTable 内部一致:+上下 padding)
|
// 表头高 & 行高(与 drawHeader/drawTable 内部一致:+上下 padding)
|
||||||
const int headerH = maxLineH + 2 * padY;
|
const int headerH = maxLineH + 2 * padY;
|
||||||
const int rowH = maxLineH + 2 * padY;
|
const int rowH = maxLineH + 2 * padY;
|
||||||
const int rowsH = rowH * rowsPerPage;
|
const int rowsH = rowH * rowsPerPage;
|
||||||
|
|
||||||
// 页脚:
|
// 页脚:
|
||||||
const int pageTextH = textheight(LPCTSTR(pageNumtext.c_str()));
|
const int pageTextH = textheight(LPCTSTR(pageNumtext.c_str()));
|
||||||
const int btnTextH = textheight(LPCTSTR("上一页"));
|
const int btnTextH = textheight(LPCTSTR("上一页"));
|
||||||
const int btnPadV = TABLE_BTN_TEXT_PAD_V;
|
const int btnPadV = TABLE_BTN_TEXT_PAD_V;
|
||||||
const int btnH = btnTextH + 2 * btnPadV;
|
const int btnH = btnTextH + 2 * btnPadV;
|
||||||
const int footerPad = TABLE_FOOTER_PAD;
|
const int footerPad = TABLE_FOOTER_PAD;
|
||||||
const int footerH = (pageTextH > btnH ? pageTextH : btnH) + footerPad;
|
const int footerH = (pageTextH > btnH ? pageTextH : btnH) + footerPad;
|
||||||
|
|
||||||
// 最终表宽/高:内容 + 对称边框
|
// 最终表宽/高:内容 + 对称边框
|
||||||
this->width = contentW + (border << 1);
|
this->width = contentW + (border << 1);
|
||||||
this->height = headerH + rowsH + footerH + (border << 1);
|
this->height = headerH + rowsH + footerH + (border << 1);
|
||||||
// 记录原始宽高用于锚点布局的参考;此处仅在初始化单元尺寸时重置
|
// 记录原始宽高用于锚点布局的参考;此处仅在初始化单元尺寸时重置
|
||||||
this->localWidth = this->width;
|
this->localWidth = this->width;
|
||||||
this->localHeight = this->height;
|
this->localHeight = this->height;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::initButton()
|
void Table::initButton()
|
||||||
@@ -132,7 +131,6 @@ void Table::initButton()
|
|||||||
int nextW = textwidth(LPCTSTR(TABLE_STR_NEXT)) + padH * 2;
|
int nextW = textwidth(LPCTSTR(TABLE_STR_NEXT)) + padH * 2;
|
||||||
int btnH = lblH + padV * 2;
|
int btnH = lblH + padV * 2;
|
||||||
|
|
||||||
|
|
||||||
// 基于“页码标签”的矩形来摆放:
|
// 基于“页码标签”的矩形来摆放:
|
||||||
// prev 在页码左侧 gap 处;next 在右侧 gap 处;Y 对齐 pY
|
// prev 在页码左侧 gap 处;next 在右侧 gap 处;Y 对齐 pY
|
||||||
int prevX = pX - gap - prevW;
|
int prevX = pX - gap - prevW;
|
||||||
@@ -162,21 +160,36 @@ void Table::initButton()
|
|||||||
|
|
||||||
prevButton->setOnClickListener([this]()
|
prevButton->setOnClickListener([this]()
|
||||||
{
|
{
|
||||||
if (currentPage > 1)
|
int oldPage = currentPage;
|
||||||
{
|
if (currentPage > 1)
|
||||||
--currentPage;
|
{
|
||||||
dirty = true;
|
--currentPage;
|
||||||
if (pageNum) pageNum->setDirty(true);
|
SX_LOGI("Table")
|
||||||
}
|
<< SX_T("翻页:id=", "page change: id=") << id
|
||||||
|
<< " " << oldPage << "->" << currentPage
|
||||||
|
<< SX_T(" 总页数=", " total=") << totalPages
|
||||||
|
<< SX_T(" 行数=", " rows=") << (int)data.size();
|
||||||
|
|
||||||
|
|
||||||
|
dirty = true;
|
||||||
|
if (pageNum) pageNum->setDirty(true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
nextButton->setOnClickListener([this]()
|
nextButton->setOnClickListener([this]()
|
||||||
{
|
{
|
||||||
if (currentPage < totalPages)
|
int oldPage = currentPage;
|
||||||
{
|
if (currentPage < totalPages)
|
||||||
++currentPage;
|
{
|
||||||
dirty = true;
|
++currentPage;
|
||||||
if (pageNum) pageNum->setDirty(true);
|
SX_LOGI("Table")
|
||||||
}
|
<< SX_T("翻页:id=", "page change: id=") << id
|
||||||
|
<< " " << oldPage << "->" << currentPage
|
||||||
|
<< SX_T(" 总页数=", " total=") << totalPages
|
||||||
|
<< SX_T(" 行数=", " rows=") << (int)data.size();
|
||||||
|
|
||||||
|
dirty = true;
|
||||||
|
if (pageNum) pageNum->setDirty(true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
isNeedButtonAndPageNum = false;
|
isNeedButtonAndPageNum = false;
|
||||||
}
|
}
|
||||||
@@ -199,7 +212,7 @@ void Table::initPageNum()
|
|||||||
// 按理来说 x + (this->width - textW) / 2;就可以
|
// 按理来说 x + (this->width - textW) / 2;就可以
|
||||||
// 但是在绘制时,发现控件偏右,因此减去40
|
// 但是在绘制时,发现控件偏右,因此减去40
|
||||||
int textW = textwidth(LPCTSTR(pageNumtext.c_str()));
|
int textW = textwidth(LPCTSTR(pageNumtext.c_str()));
|
||||||
pX = x + TABLE_PAGE_TEXT_OFFSET_X +(this->width - textW) / 2;
|
pX = x + TABLE_PAGE_TEXT_OFFSET_X + (this->width - textW) / 2;
|
||||||
|
|
||||||
if (!pageNum)
|
if (!pageNum)
|
||||||
pageNum = new Label(pX, pY, pageNumtext);
|
pageNum = new Label(pX, pY, pageNumtext);
|
||||||
@@ -216,9 +229,8 @@ void Table::initPageNum()
|
|||||||
|
|
||||||
void Table::drawPageNum()
|
void Table::drawPageNum()
|
||||||
{
|
{
|
||||||
|
|
||||||
pageNumtext = "第";
|
pageNumtext = "第";
|
||||||
pageNumtext+= std::to_string(currentPage);
|
pageNumtext += std::to_string(currentPage);
|
||||||
pageNumtext += "页/共";
|
pageNumtext += "页/共";
|
||||||
pageNumtext += std::to_string(totalPages);
|
pageNumtext += std::to_string(totalPages);
|
||||||
pageNumtext += "页";
|
pageNumtext += "页";
|
||||||
@@ -229,12 +241,11 @@ void Table::drawPageNum()
|
|||||||
if (StellarX::FillMode::Null == tableFillMode)
|
if (StellarX::FillMode::Null == tableFillMode)
|
||||||
pageNum->setTextdisap(true);
|
pageNum->setTextdisap(true);
|
||||||
pageNum->draw();
|
pageNum->draw();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::drawButton()
|
void Table::drawButton()
|
||||||
{
|
{
|
||||||
if ((nullptr == prevButton || nullptr == nextButton)|| isNeedButtonAndPageNum)
|
if ((nullptr == prevButton || nullptr == nextButton) || isNeedButtonAndPageNum)
|
||||||
initButton();
|
initButton();
|
||||||
|
|
||||||
this->prevButton->textStyle = this->textStyle;
|
this->prevButton->textStyle = this->textStyle;
|
||||||
@@ -247,7 +258,6 @@ void Table::drawButton()
|
|||||||
this->nextButton->setDirty(true);
|
this->nextButton->setDirty(true);
|
||||||
prevButton->draw();
|
prevButton->draw();
|
||||||
nextButton->draw();
|
nextButton->draw();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::setX(int x)
|
void Table::setX(int x)
|
||||||
@@ -266,35 +276,36 @@ void Table::setY(int y)
|
|||||||
|
|
||||||
void Table::setWidth(int width)
|
void Table::setWidth(int width)
|
||||||
{
|
{
|
||||||
// 调整列宽以匹配新的表格总宽度。不修改 localWidth,避免累计误差。
|
// 调整列宽以匹配新的表格总宽度。不修改 localWidth,避免累计误差。
|
||||||
// 当 width 与当前 width 不同时,根据差值平均分配到各列,余数依次累加/扣减。
|
// 当 width 与当前 width 不同时,根据差值平均分配到各列,余数依次累加/扣减。
|
||||||
const int ncols = static_cast<int>(colWidths.size());
|
const int ncols = static_cast<int>(colWidths.size());
|
||||||
if (ncols <= 0) {
|
if (ncols <= 0) {
|
||||||
this->width = width;
|
this->width = width;
|
||||||
isNeedButtonAndPageNum = true;
|
isNeedButtonAndPageNum = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int diff = width - this->width;
|
int diff = width - this->width;
|
||||||
// 基础增量:整除部分
|
// 基础增量:整除部分
|
||||||
int baseChange = diff / ncols;
|
int baseChange = diff / ncols;
|
||||||
int remainder = diff % ncols;
|
int remainder = diff % ncols;
|
||||||
for (int i = 0; i < ncols; ++i) {
|
for (int i = 0; i < ncols; ++i) {
|
||||||
int change = baseChange;
|
int change = baseChange;
|
||||||
if (remainder > 0) {
|
if (remainder > 0) {
|
||||||
change += 1;
|
change += 1;
|
||||||
remainder -= 1;
|
remainder -= 1;
|
||||||
} else if (remainder < 0) {
|
}
|
||||||
change -= 1;
|
else if (remainder < 0) {
|
||||||
remainder += 1;
|
change -= 1;
|
||||||
}
|
remainder += 1;
|
||||||
int newWidth = colWidths[i] + change;
|
}
|
||||||
// 限制最小宽度为 1,防止出现负值
|
int newWidth = colWidths[i] + change;
|
||||||
if (newWidth < 1) newWidth = 1;
|
// 限制最小宽度为 1,防止出现负值
|
||||||
colWidths[i] = newWidth;
|
if (newWidth < 1) newWidth = 1;
|
||||||
}
|
colWidths[i] = newWidth;
|
||||||
this->width = width;
|
}
|
||||||
// 需要重新布局页脚元素
|
this->width = width;
|
||||||
isNeedButtonAndPageNum = true;
|
// 需要重新布局页脚元素
|
||||||
|
isNeedButtonAndPageNum = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::setHeight(int height)
|
void Table::setHeight(int height)
|
||||||
@@ -377,32 +388,30 @@ void Table::draw()
|
|||||||
setfillstyle((int)tableFillMode);
|
setfillstyle((int)tableFillMode);
|
||||||
setbkmode(TRANSPARENT);
|
setbkmode(TRANSPARENT);
|
||||||
}
|
}
|
||||||
// 在绘制前先恢复并更新背景快照:
|
// 在绘制前先恢复并更新背景快照:
|
||||||
// 如果已有快照且尺寸发生变化,先恢复旧快照以清除上一次绘制,然后丢弃旧快照再重新抓取新的区域。
|
// 如果已有快照且尺寸发生变化,先恢复旧快照以清除上一次绘制,然后丢弃旧快照再重新抓取新的区域。
|
||||||
if (hasSnap)
|
if (hasSnap)
|
||||||
{
|
{
|
||||||
// 始终先恢复旧背景,清除上一帧内容
|
// 始终先恢复旧背景,清除上一帧内容
|
||||||
restBackground();
|
restBackground();
|
||||||
// 当尺寸变化或缓存图像无效时,需要重新截图
|
// 当尺寸变化或缓存图像无效时,需要重新截图
|
||||||
if (!saveBkImage || saveWidth != this->width || saveHeight != this->height)
|
if (!saveBkImage || saveWidth != this->width || saveHeight != this->height)
|
||||||
{
|
{
|
||||||
discardBackground();
|
discardBackground();
|
||||||
saveBackground(this->x, this->y, this->width, this->height);
|
saveBackground(this->x, this->y, this->width, this->height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 首次绘制时无背景缓存,直接抓取
|
// 首次绘制时无背景缓存,直接抓取
|
||||||
saveBackground(this->x, this->y, this->width, this->height);
|
saveBackground(this->x, this->y, this->width, this->height);
|
||||||
}
|
}
|
||||||
// 恢复最新的背景,保证绘制区域干净
|
// 恢复最新的背景,保证绘制区域干净
|
||||||
restBackground();
|
restBackground();
|
||||||
// 绘制表头
|
// 绘制表头
|
||||||
|
|
||||||
dX = x;
|
//if (!headers.empty())
|
||||||
dY = y;
|
drawHeader();
|
||||||
drawHeader();
|
|
||||||
this->isNeedDrawHeaders = false;
|
|
||||||
// 绘制当前页
|
// 绘制当前页
|
||||||
drawTable();
|
drawTable();
|
||||||
// 绘制页码标签
|
// 绘制页码标签
|
||||||
@@ -412,7 +421,6 @@ void Table::draw()
|
|||||||
if (this->isShowPageButton)
|
if (this->isShowPageButton)
|
||||||
drawButton();
|
drawButton();
|
||||||
|
|
||||||
|
|
||||||
// 恢复绘图状态
|
// 恢复绘图状态
|
||||||
restoreStyle();
|
restoreStyle();
|
||||||
dirty = false; // 标记不需要重绘
|
dirty = false; // 标记不需要重绘
|
||||||
@@ -421,14 +429,14 @@ void Table::draw()
|
|||||||
|
|
||||||
bool Table::handleEvent(const ExMessage& msg)
|
bool Table::handleEvent(const ExMessage& msg)
|
||||||
{
|
{
|
||||||
if(!show)return false;
|
if (!show)return false;
|
||||||
bool consume = false;
|
bool consume = false;
|
||||||
if(!this->isShowPageButton)
|
if (!this->isShowPageButton)
|
||||||
return consume;
|
return consume;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(prevButton)consume = prevButton->handleEvent(msg);
|
if (prevButton)consume = prevButton->handleEvent(msg);
|
||||||
if (nextButton&&!consume)
|
if (nextButton && !consume)
|
||||||
consume = nextButton->handleEvent(msg);
|
consume = nextButton->handleEvent(msg);
|
||||||
}
|
}
|
||||||
if (dirty)
|
if (dirty)
|
||||||
@@ -441,30 +449,41 @@ void Table::setHeaders(std::initializer_list<std::string> headers)
|
|||||||
this->headers.clear();
|
this->headers.clear();
|
||||||
for (auto& lis : headers)
|
for (auto& lis : headers)
|
||||||
this->headers.push_back(lis);
|
this->headers.push_back(lis);
|
||||||
|
SX_LOGI("Table") << SX_T("设置表头:id=","setHeaders: id=") << id << SX_T("总数="," count=") << (int)this->headers.size();
|
||||||
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
||||||
isNeedDrawHeaders = true; // 标记需要重新绘制表头
|
isNeedDrawHeaders = true; // 标记需要重新绘制表头
|
||||||
dirty = true;
|
dirty = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::setData( std::vector<std::string> data)
|
void Table::setData(std::vector<std::string> data)
|
||||||
{
|
{
|
||||||
if (data.size() < headers.size())
|
while (data.size() < headers.size())
|
||||||
for (int i = 0; data.size() <= headers.size(); i++)
|
data.push_back("");
|
||||||
data.push_back("");
|
|
||||||
this->data.push_back(data);
|
this->data.push_back(data);
|
||||||
|
|
||||||
totalPages = ((int)this->data.size() + rowsPerPage - 1) / rowsPerPage;
|
totalPages = ((int)this->data.size() + rowsPerPage - 1) / rowsPerPage;
|
||||||
if (totalPages < 1)
|
if (totalPages < 1)
|
||||||
totalPages = 1;
|
totalPages = 1;
|
||||||
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
|
||||||
|
isNeedCellSize = true;
|
||||||
dirty = true;
|
dirty = true;
|
||||||
|
|
||||||
|
SX_LOGI("Table")
|
||||||
|
<< SX_T("新增Data:id=", "appendRow: id=") << id
|
||||||
|
<< SX_T(" 本行列数=", " cols=") << (int)data.size()
|
||||||
|
<< SX_T(" 数据总行数=", " totalRows=") << (int)this->data.size()
|
||||||
|
<< SX_T(" 总页数=", " totalPages=") << totalPages;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::setData( std::initializer_list<std::vector<std::string>> data)
|
|
||||||
|
void Table::setData(std::initializer_list<std::vector<std::string>> data)
|
||||||
{
|
{
|
||||||
for (auto lis : data)
|
for (auto lis : data)
|
||||||
if (lis.size() < headers.size())
|
if (lis.size() < headers.size())
|
||||||
{
|
{
|
||||||
for (size_t i = lis.size(); i< headers.size(); i++)
|
for (size_t i = lis.size(); i < headers.size(); i++)
|
||||||
lis.push_back("");
|
lis.push_back("");
|
||||||
this->data.push_back(lis);
|
this->data.push_back(lis);
|
||||||
}
|
}
|
||||||
@@ -476,6 +495,13 @@ void Table::setData( std::initializer_list<std::vector<std::string>> data)
|
|||||||
totalPages = 1;
|
totalPages = 1;
|
||||||
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
||||||
dirty = true;
|
dirty = true;
|
||||||
|
SX_LOGI("Table")
|
||||||
|
<< SX_T("新增Data:id=", "appendRow: id=") << id
|
||||||
|
<< SX_T(" 本行列数=", " cols=") << (int)data.size()
|
||||||
|
<< SX_T(" 数据总行数=", " totalRows=") << (int)this->data.size()
|
||||||
|
<< SX_T(" 总页数=", " totalPages=") << totalPages;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::setRowsPerPage(int rows)
|
void Table::setRowsPerPage(int rows)
|
||||||
@@ -538,6 +564,34 @@ void Table::setTableBorderWidth(int width)
|
|||||||
this->dirty = true;
|
this->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Table::clearHeaders()
|
||||||
|
{
|
||||||
|
this->headers.clear();
|
||||||
|
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
||||||
|
isNeedDrawHeaders = true; // 标记需要重新绘制表头
|
||||||
|
isNeedButtonAndPageNum = true;// 标记需要重新计算翻页按钮和页码信息
|
||||||
|
dirty = true;
|
||||||
|
SX_LOGI("Table") << SX_T("清除表头:id=","clearHeaders: id=" )<< id;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Table::clearData()
|
||||||
|
{
|
||||||
|
this->data.clear();
|
||||||
|
this->currentPage = 1;
|
||||||
|
this->totalPages = 1;
|
||||||
|
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
||||||
|
isNeedButtonAndPageNum = true;// 标记需要重新计算翻页按钮和页码信息
|
||||||
|
dirty = true;
|
||||||
|
SX_LOGI("Table") << SX_T("清除表格数据:id=","clearData: id=") << id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Table::resetTable()
|
||||||
|
{
|
||||||
|
clearHeaders();
|
||||||
|
clearData();
|
||||||
|
}
|
||||||
|
|
||||||
void Table::onWindowResize()
|
void Table::onWindowResize()
|
||||||
{
|
{
|
||||||
Control::onWindowResize(); // 先处理自己
|
Control::onWindowResize(); // 先处理自己
|
||||||
@@ -616,5 +670,3 @@ int Table::getTableHeight() const
|
|||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
268
src/textBox.cpp
268
src/textBox.cpp
@@ -1,165 +1,209 @@
|
|||||||
// TextBox.cpp
|
// TextBox.cpp
|
||||||
#include "TextBox.h"
|
#include "TextBox.h"
|
||||||
|
#include "SxLog.h"
|
||||||
|
|
||||||
TextBox::TextBox(int x, int y, int width, int height, std::string text, StellarX::TextBoxmode mode, StellarX::ControlShape shape)
|
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)
|
:Control(x, y, width, height), text(text), mode(mode), shape(shape)
|
||||||
{
|
{
|
||||||
this->id = "TextBox";
|
this->id = "TextBox";
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBox::draw()
|
void TextBox::draw()
|
||||||
{
|
{
|
||||||
if (dirty && show)
|
if (dirty && show)
|
||||||
{
|
{
|
||||||
saveStyle();
|
saveStyle();
|
||||||
setfillcolor(textBoxBkClor);
|
setfillcolor(textBoxBkClor);
|
||||||
setlinecolor(textBoxBorderClor);
|
setlinecolor(textBoxBorderClor);
|
||||||
if (textStyle.nHeight > height)
|
if (textStyle.nHeight > height)
|
||||||
textStyle.nHeight = height;
|
textStyle.nHeight = height;
|
||||||
if (textStyle.nWidth > width)
|
if (textStyle.nWidth > width)
|
||||||
textStyle.nWidth = width;
|
textStyle.nWidth = width;
|
||||||
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);
|
||||||
|
|
||||||
settextcolor(textStyle.color);
|
settextcolor(textStyle.color);
|
||||||
setbkmode(TRANSPARENT);
|
setbkmode(TRANSPARENT);
|
||||||
int text_width = textwidth(LPCTSTR(text.c_str()));
|
|
||||||
int text_height = textheight(LPCTSTR(text.c_str()));
|
|
||||||
|
|
||||||
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
|
int text_width = 0;
|
||||||
saveBackground(this->x, this->y, this->width, this->height);
|
int text_height = 0;
|
||||||
// 恢复背景(清除旧内容)
|
std::string pwdText;
|
||||||
restBackground();
|
if (StellarX::TextBoxmode::PASSWORD_MODE == mode)
|
||||||
//根据形状绘制
|
{
|
||||||
switch (shape)
|
for (size_t i = 0; i < text.size(); ++i)
|
||||||
{
|
pwdText += '*';
|
||||||
case StellarX::ControlShape::RECTANGLE:
|
text_width = textwidth(LPCTSTR(pwdText.c_str()));
|
||||||
fillrectangle(x,y,x+width,y+height);//有边框填充矩形
|
text_height = textheight(LPCTSTR(pwdText.c_str()));
|
||||||
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
}
|
||||||
break;
|
else
|
||||||
case StellarX::ControlShape::B_RECTANGLE:
|
{
|
||||||
solidrectangle(x, y, x + width, y + height);//无边框填充矩形
|
text_width = textwidth(LPCTSTR(text.c_str()));
|
||||||
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
text_height = textheight(LPCTSTR(text.c_str()));
|
||||||
break;
|
}
|
||||||
case StellarX::ControlShape::ROUND_RECTANGLE:
|
|
||||||
fillroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);//有边框填充圆角矩形
|
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
|
||||||
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
saveBackground(this->x, this->y, this->width, this->height);
|
||||||
break;
|
// 恢复背景(清除旧内容)
|
||||||
case StellarX::ControlShape::B_ROUND_RECTANGLE:
|
restBackground();
|
||||||
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()));
|
switch (shape)
|
||||||
break;
|
{
|
||||||
}
|
case StellarX::ControlShape::RECTANGLE:
|
||||||
|
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(text.c_str()));
|
||||||
|
break;
|
||||||
|
case StellarX::ControlShape::B_RECTANGLE:
|
||||||
|
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(text.c_str()));
|
||||||
|
break;
|
||||||
|
case StellarX::ControlShape::ROUND_RECTANGLE:
|
||||||
|
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(text.c_str()));
|
||||||
|
break;
|
||||||
|
case StellarX::ControlShape::B_ROUND_RECTANGLE:
|
||||||
|
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(text.c_str()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
restoreStyle();
|
restoreStyle();
|
||||||
dirty = false; //标记不需要重绘
|
dirty = false; //标记不需要重绘
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TextBox::handleEvent(const ExMessage& msg)
|
bool TextBox::handleEvent(const ExMessage& msg)
|
||||||
{
|
{
|
||||||
bool hover = false;
|
if (!show) return false;
|
||||||
bool oldClick = click;
|
|
||||||
bool consume = false;
|
|
||||||
|
|
||||||
switch (shape)
|
bool hover = false;
|
||||||
{
|
bool oldClick = click;
|
||||||
case StellarX::ControlShape::RECTANGLE:
|
bool consume = false;
|
||||||
case StellarX::ControlShape::B_RECTANGLE:
|
|
||||||
case StellarX::ControlShape::ROUND_RECTANGLE:
|
switch (shape)
|
||||||
case StellarX::ControlShape::B_ROUND_RECTANGLE:
|
{
|
||||||
hover = (msg.x > x && msg.x < (x + width) && msg.y > y && msg.y < (y + height));//判断鼠标是否在矩形按钮内
|
case StellarX::ControlShape::RECTANGLE:
|
||||||
consume = false;
|
case StellarX::ControlShape::B_RECTANGLE:
|
||||||
break;
|
case StellarX::ControlShape::ROUND_RECTANGLE:
|
||||||
}
|
case StellarX::ControlShape::B_ROUND_RECTANGLE:
|
||||||
if (hover && msg.message == WM_LBUTTONUP)
|
hover = (msg.x > x && msg.x < (x + width) && msg.y > y && msg.y < (y + height));
|
||||||
{
|
break;
|
||||||
click = true;
|
default:
|
||||||
if(StellarX::TextBoxmode::INPUT_MODE == mode)
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hover && msg.message == WM_LBUTTONUP)
|
||||||
|
{
|
||||||
|
click = true;
|
||||||
|
|
||||||
|
const size_t oldLen = text.size();
|
||||||
|
SX_LOGI("TextBox") << SX_T("激活:id=","activate: id=") << id << " mode=" << (int)mode << " oldLen=" << oldLen;
|
||||||
|
|
||||||
|
if (StellarX::TextBoxmode::INPUT_MODE == mode)
|
||||||
{
|
{
|
||||||
char* temp = new char[maxCharLen + 1];
|
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;
|
if (dirty) text = temp;
|
||||||
delete[] temp;
|
delete[] temp;
|
||||||
temp = nullptr;
|
consume = true;
|
||||||
consume = true;
|
}
|
||||||
|
else if (StellarX::TextBoxmode::READONLY_MODE == mode)
|
||||||
|
{
|
||||||
|
dirty = false;
|
||||||
|
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;
|
||||||
|
consume = true;
|
||||||
}
|
}
|
||||||
else if (StellarX::TextBoxmode::READONLY_MODE == mode)
|
|
||||||
{
|
|
||||||
dirty = false;
|
|
||||||
InputBox(NULL, (int)maxCharLen, "输出框(输入无效!)", NULL, text.c_str(), NULL, NULL, false);
|
|
||||||
consume = true;
|
|
||||||
}
|
|
||||||
flushmessage(EX_MOUSE | EX_KEY);
|
|
||||||
}
|
|
||||||
if (dirty)
|
|
||||||
requestRepaint(parent);
|
|
||||||
|
|
||||||
if (click)
|
if (dirty)
|
||||||
click = false;
|
{
|
||||||
return consume;
|
SX_LOGI("TextBox") << SX_T("文本已更改: id=","text changed: id=") << id
|
||||||
|
<< " oldLen=" << oldLen << " newLen=" << text.size();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SX_LOGD("TextBox") << SX_T("文本无变化:id=","no change: id=") << id;
|
||||||
|
}
|
||||||
|
|
||||||
|
flushmessage(EX_MOUSE | EX_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dirty)
|
||||||
|
requestRepaint(parent);
|
||||||
|
|
||||||
|
if (click)
|
||||||
|
click = false;
|
||||||
|
|
||||||
|
return consume;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void TextBox::setMode(StellarX::TextBoxmode mode)
|
void TextBox::setMode(StellarX::TextBoxmode mode)
|
||||||
{
|
{
|
||||||
this->mode = mode;
|
this->mode = mode;
|
||||||
this->dirty = true;
|
this->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBox::setMaxCharLen(size_t len)
|
void TextBox::setMaxCharLen(size_t len)
|
||||||
{
|
{
|
||||||
if (len > 0)
|
if (len > 0)
|
||||||
maxCharLen = len;
|
maxCharLen = len;
|
||||||
this->dirty = true;
|
this->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBox::setTextBoxshape(StellarX::ControlShape shape)
|
void TextBox::setTextBoxshape(StellarX::ControlShape shape)
|
||||||
{
|
{
|
||||||
switch (shape)
|
switch (shape)
|
||||||
{
|
{
|
||||||
case StellarX::ControlShape::RECTANGLE:
|
case StellarX::ControlShape::RECTANGLE:
|
||||||
case StellarX::ControlShape::B_RECTANGLE:
|
case StellarX::ControlShape::B_RECTANGLE:
|
||||||
case StellarX::ControlShape::ROUND_RECTANGLE:
|
case StellarX::ControlShape::ROUND_RECTANGLE:
|
||||||
case StellarX::ControlShape::B_ROUND_RECTANGLE:
|
case StellarX::ControlShape::B_ROUND_RECTANGLE:
|
||||||
this->shape = shape;
|
this->shape = shape;
|
||||||
this->dirty = true;
|
this->dirty = true;
|
||||||
break;
|
break;
|
||||||
case StellarX::ControlShape::CIRCLE:
|
case StellarX::ControlShape::CIRCLE:
|
||||||
case StellarX::ControlShape::B_CIRCLE:
|
case StellarX::ControlShape::B_CIRCLE:
|
||||||
case StellarX::ControlShape::ELLIPSE:
|
case StellarX::ControlShape::ELLIPSE:
|
||||||
case StellarX::ControlShape::B_ELLIPSE:
|
case StellarX::ControlShape::B_ELLIPSE:
|
||||||
this->shape = StellarX::ControlShape::RECTANGLE;
|
this->shape = StellarX::ControlShape::RECTANGLE;
|
||||||
this->dirty = true;
|
this->dirty = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBox::setTextBoxBorder(COLORREF color)
|
void TextBox::setTextBoxBorder(COLORREF color)
|
||||||
{
|
{
|
||||||
textBoxBorderClor = color;
|
textBoxBorderClor = color;
|
||||||
this->dirty = true;
|
this->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBox::setTextBoxBk(COLORREF color)
|
void TextBox::setTextBoxBk(COLORREF color)
|
||||||
{
|
{
|
||||||
textBoxBkClor = color;
|
textBoxBkClor = color;
|
||||||
this->dirty = true;
|
this->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBox::setText(std::string text)
|
void TextBox::setText(std::string text)
|
||||||
{
|
{
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string TextBox::getText() const
|
std::string TextBox::getText() const
|
||||||
{
|
{
|
||||||
return this->text;
|
return this->text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1129
src/window.cpp
1129
src/window.cpp
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user