feat: add a new awesome feature

This commit is contained in:
2025-11-02 18:04:55 +08:00
parent c4852d080f
commit 4bb0352088
23 changed files with 4712 additions and 1899 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[中文文档](CHANGELOG.md)
## [v2.2.0] - 2025-11-02
**Highlights**: Officially introduces the TabControl, enhances control show/hide and layout responsiveness, and refines the text styling mechanism; fixes several UI details to improve stability.
### ✨ Added
- **TabControl (tabbed container control)**: Added the `TabControl` class to implement a multi-page tabbed UI. The tab bar supports **top/bottom/left/right** positions via `TabControl::setTabPlacement(...)`. Provides `TabControl::add(std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>&&)` to add a “tab button + page content” pair in one go and automatically manage switching. Each pages content area is hosted by a `Canvas`; clicking different tabs (Button, TOGGLE mode) switches the visible page **(see API)**. TabControl can automatically adjust tab layout when the window size changes and uses background snapshots to avoid ghosting under transparent themes.
- **Control visibility**: All controls now support runtime **show/hide** toggling. The base class adds `Control::setIsVisible(bool)` to control a controls own visibility; hidden controls no longer participate in drawing and events. Container controls (`Canvas`) override this to implement **cascading hide**: hiding a container automatically hides all its child controls, and showing it restores them. This makes it more convenient to toggle the visibility of a group of UI elements.
- **Window resize handling**: Introduces the virtual function `Control::onWindowResize()`, called when the parent window size changes. Controls can implement this to respond to window resizing (e.g., reset background caches, adjust layout). `Canvas` implements this and **recursively notifies child controls** to call `onWindowResize()`, ensuring nested layouts update correctly. This improvement fixes possible misalignment or background issues that occurred after resizing.
- **Label text style structure**: The `Label` control now uses a unified `ControlText` style structure to manage fonts and colors. By accessing the public member `Label::textStyle`, you can set font name, size, color, underline, etc., achieving **full text style customization** (replacing the previous `setTextColor` interface). This enables richer formatting when displaying text with Label.
- **Dialog de-duplication mechanism**: `Window` adds an internal check to **prevent popping up duplicate dialogs with the same content**. When using `MessageBox::showAsync` for a non-modal popup, the framework checks if a dialog with the same title and message is already open; if so, it avoids creating another one. This prevents multiple identical prompts from appearing in quick succession.
### ⚙️ Changed
- **Text color interface adjustment**: `Label` removes the outdated `setTextColor` method; use the public `textStyle.color` to set text color instead. To change a Labels text color, modify `label.textStyle.color` and redraw. This improves consistency in text property management but may be incompatible with older code and require replacement.
- **Tooltip styling and toggle**: The `Button` Tooltip interface is adjusted to support more customization. `Button::setTooltipStyle` can now flexibly set tooltip text color, background color, and transparency; `setTooltipTextsForToggle(onText, offText)` is added so toggle buttons can display different tooltip texts in **ON/OFF** states. The original tooltip-text setting interface remains compatible, but the internal implementation is optimized, fixing the previous issue where toggle-button tooltip text didnt update.
- **Control coordinate system and layout**: Controls now maintain both **global coordinates** and **local coordinates**. `Control` adds members (such as `getLocalX()/getLocalY()`) to get positions relative to the parent container, and a `parent` pointer to the parent container. This makes layout calculations in nested containers more convenient and accurate. In absolute-layout scenarios, a controls global coordinates are automatically converted and stored as local coordinates when added to a container. Note: in the new version, when moving controls or changing sizes, prefer using the controls own setters to keep internal coordinates in sync.
- **Window resizing defaults**: Resizing is changed from experimental to **enabled by default**. The framework always enables resizable styles for the main window (`WS_THICKFRAME | WS_MAXIMIZEBOX | ...`), so theres no need to call a separate method to enable it. This simplifies usage and means created windows can be resized by users by default. For scenarios where resizing is not desired, pass specific mode flags at creation time to disable it.
### ✅ Fixed
- **Toggle-button tooltip updates**: Fixed an issue where tooltips for toggle-mode buttons did not update promptly when the state changed. With `setTooltipTextsForToggle`, the tooltip now correctly shows the corresponding text when switching between **ON/OFF**.
- **Background ghosting and coordinate sync for controls**: Fixed defects where, in some cases, control backgrounds were not refreshed in time and position calculations deviated after window/container resizing. By using `onWindowResize` to uniformly discard and update background snapshots, background ghosting and misalignment during window resizing are avoided, improving overall stability.
- **`Control` class background-snapshot memory leak**: The destructor now calls `discardBackground` to free and restore the background snapshot, preventing the memory leak caused by not releasing `*saveBkImage` in the previous version.
- **Duplicate dialog pop-ups**: Fixed an issue where repeatedly calling a non-modal message box in quick succession could create multiple identical dialogs. The new de-duplication check ensures that only one non-modal dialog with the same content exists at a time, avoiding UI disruption.
- **Other**: Optimized the control drawing/refresh strategy to reduce unnecessary repaints in certain scenarios and improve performance; corrected minor memory-management details to eliminate potential leaks. These enhancements further improve the frameworks performance and reliability.
## [v2.1.0] - 2025-10-27
**Focus**: Resizable/maximized window (EasyX reinforced by Win32), first-phase layout manager (HBox/VBox), early Tabs control. We also fixed black borders, maximize “ghosts”, flicker, and the issue where controls only appeared after interaction. Control-level **background snapshot/restore**, **single-line truncation**, and **tooltips** are now standardized.

View File

@@ -7,6 +7,33 @@ StellarX 项目所有显著的变化都将被记录在这个文件中。
[English document](CHANGELOG.en.md)
## [v2.2.0] - 2025-11-02
**重点**:正式引入选项卡控件(TabControl)增强控件显隐与布局响应能力并完善文本样式机制修复若干UI细节问题以提升稳定性。
### ✨ 新增
- **选项卡容器控件 TabControl**:新增 `TabControl` 类,实现多页面选项卡界面。支持页签栏在 **上/下/左/右** 四种位置排列,可通过 `TabControl::setTabPlacement(...)` 设置。提供 `TabControl::add(std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>&&)` 接口,一次性添加“页签按钮+页面内容”对,并自动管理切换显示。各页内容区域由 `Canvas` 承载,点击不同页签(ButtonTOGGLE模式)将切换显示对应页面**【见 API】**。TabControl 在窗口大小变化时可自动调整页签布局,并使用背景快照避免透明主题下的叠影问题。
- **控件显隐控制**:所有控件现支持运行时**显示/隐藏**切换。基类新增方法 `Control::setIsVisible(bool)` 控制自身可见性,隐藏后控件不参与绘制和事件。容器控件(`Canvas`)重写该方法,实现**级联隐藏**:隐藏容器将自动隐藏其中所有子控件,再次显示时子控件也随之恢复。这使一组界面元素的显隐切换更加方便。
- **窗口尺寸变化响应**:引入 `Control::onWindowResize()` 虚函数,当父窗口尺寸改变时调用。各控件可通过实现此函数响应窗口大小调整,例如重置背景缓存、调整布局等。`Canvas` 已实现该函数,会在窗口变化时**递归通知子控件**调用 `onWindowResize()`,确保嵌套布局能正确更新。此改进解决了之前窗口拉伸后子控件可能位置错位或背景异常的问题。
- **Label 文本样式结构**`Label` 控件现在使用统一的 `ControlText` 样式结构管理字体和颜色。可通过访问公有成员 `Label::textStyle` 设置字体名称、大小、颜色、下划线等属性,实现**完整文本样式定制**(替代原先的 setTextColor 接口)。这使 Label 在展示文本时支持更丰富的格式。
- **Dialog 防重复机制**`Window` 新增内部检查函数,**防止重复弹出相同内容的对话框**。在使用 `MessageBox::showAsync` 非模态弹窗时,框架会判断是否已有相同标题和消息的对话框未关闭,若是则避免再次创建。此机制杜绝了短时间内弹出多个相同提示的情况。
### ⚙️ 变更
- **文本颜色接口调整**`Label` 移除了过时的 `setTextColor` 方法,改为通过公有的 `textStyle.color` 设置文字颜色。如需改变 Label 文本颜色,请直接修改 `label.textStyle.color` 并重绘。此改动提升了文本属性管理的一致性,但可能对旧版代码不兼容,需要做相应替换。
- **Tooltip 样式与切换**`Button` 的悬停提示 (Tooltip) 接口调整为支持更多自定义。`Button::setTooltipStyle` 现在可灵活设置提示文字颜色、背景色及透明模式;新增 `setTooltipTextsForToggle(onText, offText)` 用于切换按钮在 **ON/OFF** 两种状态下显示不同的提示文字。原有 Tooltip 文案设置接口仍兼容,但内部实现优化,修正了先前切换按钮提示文字不更新的问题。
- **控件坐标系与布局**:控件现在同时维护**全局坐标**和**局部坐标**。`Control` 新增成员(如 `getLocalX()/getLocalY()` 等)用于获取控件相对父容器的位置,以及 `parent` 指针指向父容器。这一改进使得嵌套容器布局计算更加便捷和准确。在绝对布局场景下,控件的全局坐标会在添加进容器时自动转换为本地坐标保存。开发者需要注意,新版中移动控件或调整尺寸应优先使用控件自带的 setter以确保内部坐标同步更新。
- **默认窗口调整**:窗口拉伸功能从实验转为默认支持。框架始终为主窗口启用了可调整大小的样式(`WS_THICKFRAME|WS_MAXIMIZEBOX|...`),不再需要调用独立的方法启用。这一变更简化了使用,同时意味着创建的窗口默认可以被用户拖拽拉伸。对于不希望窗口缩放的场景,可在创建窗口时通过传递特定模式标志来禁止。
### ✅ 修复
- **切换按钮 Tooltip 更新**:修正了切换模式按钮在状态改变时悬停提示未及时更新的问题。现在使用 `setTooltipTextsForToggle` 设置不同提示后,按钮在 **ON/OFF** 状态切换时悬停提示文字会正确显示对应内容。
- **控件背景残影与坐标同步**:解决了某些情况下窗口或容器尺寸变化后控件背景未及时刷新、位置计算偏差的缺陷。利用 `onWindowResize` 统一丢弃并更新背景快照,避免了拉伸窗口时可能出现的控件背景残影和错位现象,界面稳定性提升。
- **`Control`类背景快照内存泄漏**:在析构函数里调用了`discardBackground`释放并恢复背景快照,避免了上一版本未释放`*saveBkImage`造成的内存泄漏
- **重复对话框弹出**:修复了快速重复调用非模态消息框可能出现多个相同对话框的问题。新增的去重判断保证相同内容的非模态对话框同一时间只会存在一个,避免用户界面受到干扰。
- **其他**:优化了控件绘制刷新策略,减少某些场景下的不必要重绘,提升运行效率;修正少量内存管理细节以消除潜在泄漏。上述改进进一步提高了框架的性能与可靠性。
## [v2.1.0] - 2025-10-27
**重点**:窗口可拉伸/最大化补强EasyX + Win32、布局管理器HBox/VBox 第一阶段)、选项卡控件雏形;系统性修复黑边、最大化残影、频闪与“控件需交互才出现”等历史问题。并统一了**背景快照/恢复**、**按钮单行截断**、**Tooltip** 的机制。

View File

@@ -1,14 +1,14 @@
# StellarX GUI Framework
# StellarX GUI Framework README
[中文文档](README.md)
[中文README](README.md)
------
![GitHub all releases](https://img.shields.io/github/downloads/Ysm-04/StellarX/total)
[![Star GitHub Repo](https://img.shields.io/github/stars/Ysm-04/StellarX.svg?style=social&label=Star%20This%20Repo)](https://github.com/Ysm-04/StellarX)
![Version](https://img.shields.io/badge/Version-2.1.0-brightgreen.svg)
![Download](https://img.shields.io/badge/Download-2.1.0_Release-blue.svg)
![Version](https://img.shields.io/badge/Version-2.2.0-brightgreen.svg)
![Download](https://img.shields.io/badge/Download-2.2.0_Release-blue.svg)
![C++](https://img.shields.io/badge/C++-17+-00599C?logo=cplusplus&logoColor=white)
![Windows](https://img.shields.io/badge/Platform-Windows-0078D6?logo=windows)
@@ -17,50 +17,41 @@
![Architecture](https://img.shields.io/badge/Architecture-Modular%20OOP-brightgreen)
![CMake](https://img.shields.io/badge/Build-CMake-064F8C?logo=cmake)
> **"Bound by Stars, Light as Dust"** — A native C++ GUI framework for Windows, featuring extreme lightness and high modularity.
> **Bounded by the stars, light as dust.”** — An ultra-lightweight, highly modular, native C++ GUI framework for Windows.
`StellarX` was born from resistance against bloat. It avoids huge dependencies, long build times, and steep learning curves, returning to the essence: using concise code, clear architecture, and high efficiency to solve desktop GUI needs.
`StellarX` rejects bloat: no hundreds-of-MB dependencies, no marathon builds, and no steep learning curve. Back to the essence—clean code, clear architecture, and high efficiency to solve the core needs of desktop app development.
It is a **pure teaching/tool-level framework** that helps developers understand GUI principles and quickly build lightweight Windows tools.
This is a **teaching-grade and tooling-grade** framework that helps developers understand GUI fundamentals and quickly build lightweight utilities.
---
------
## 🆕 What's New in v2.1.0
## 🆕 Whats new in v2.2.0
**Bilingual API Documentation (Chinese and English)**
- **New TabControl for multi-page tabbed UIs:** With `TabControl`, its easy to create a tabbed layout. Tabs can be arranged on the top, bottom, left, or right, and clicking switches the displayed page. Suitable for settings panels and multi-view switching.
- **Enhanced control show/hide and resize responsiveness:** All controls now share a unified interface (`setIsVisible`) to toggle visibility. When a container control is hidden, its child controls automatically hide/show with it. Meanwhile, we introduce `onWindowResize` for controls to respond to window size changes so elements update in sync after resizing, eliminating artifacts or misalignment.
- **Refined text-style mechanism:** The Label control now uses a unified `ControlText` style structure. Developers can easily customize font, color, size, etc. (replacing older interfaces, and more flexible). Button Tooltips also support richer customization and different texts for toggle states.
- **Other improvements:** Dialog management gains de-duplication to prevent identical prompts from popping up repeatedly. Several bug fixes and refresh optimizations further improve stability.
- The documentation provides a detailed introduction of each class, including API descriptions, functionalities, and points to note, with a comprehensive explanation of each control.
See `CHANGELOG.md / CHANGELOG.en.md` for the full list.
- **Resizable/Maximizable Window (EasyX + Win32 reinforcement)**
Subclassed WndProc with `WM_GETMINMAXINFO / WM_SIZE / WM_EXITSIZEMOVE / WM_ERASEBKGND / WM_PAINT` and window styles `WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CLIPCHILDREN | WS_CLIPSIBLINGS`. Full-surface background paint in `WM_PAINT` eliminates black borders and maximize “ghosts”. Coalesced resize with one-shot redraw.
- **Layout Manager (Phase 1)**
`Canvas` now supports `LayoutKind::{Absolute, HBox, VBox, Grid(placeholder), Flow(placeholder), Stack(placeholder)}` with `LayoutParams` (`margins`, `fixedW/fixedH`, `weight`, `Align{Start,Center,End,Stretch}`). Implemented **HBox/VBox** auto layout (containers remain absolutely positioned; nesting supported).
- **Tabs (early)**
Tab strip + page container separated; background snapshot avoids trails on transparent themes.
- **Button Single-Line Truncation (MBCS, CN/EN aware)**
Pixel-width based `...` truncation prevents half-glyph artifacts.
- **Hover Tooltip**
Implemented via `Label`, supports delay and auto-hide; per-control background snapshot/restore.
See details in `CHANGELOG.en.md`.
---
------
## 📦 Project Structure & Design Philosophy
StellarX adopts classic **OOP** and **modular** design with a clear structure:
```markdown
```
StellarX/
├── include/
│ └── StellarX/
│ ├── StellarX.h
│ ├── CoreTypes.h # Single source of truth for enums/structs
│ ├── CoreTypes.h # single source of truth (enums/structs)
│ ├── Control.h
│ ├── Button.h
│ ├── Window.h
│ ├── Label.h
│ ├── TextBox.h
│ ├── TabControl.h #v2.2.0
│ ├── Canvas.h
│ ├── Dialog.h
│ ├── MessageBox.h
@@ -72,6 +63,7 @@ StellarX/
│ ├── Label.cpp
│ ├── TextBox.cpp
│ ├── Canvas.cpp
│ ├── TabControl.cpp #v2.2.0
│ ├── Table.cpp
│ ├── Dialog.cpp
│ └── MessageBox.cpp
@@ -81,54 +73,52 @@ StellarX/
│ └── CODE_OF_CONDUCT.md
├── CMakeLists.txt
├── CONTRIBUTING.md
├── CHANGELOG.en.md
├── CHANGELOG.md
├── CHANGELOG.en.md
├── Doxyfile
├── LICENSE
├──API 文档.md
├──API Documentation.en.md
└── README.en.md
└── README.md
```
### Design Philosophy
**Design Philosophy:**
1. **SRP**: each class/file does one thing.
2. **DIP**: high-level modules depend on abstractions (`Control`), not concrete controls.
3. **OCP**: new controls extend `Control` without touching existing code.
4. **Consistency**: all controls expose `draw()` and `handleEvent()`.
1. **Single Responsibility (SRP):** each class/file does exactly one thing.
2. **Dependency Inversion (DIP):** high-level modules depend on abstractions (`Control`), not concrete controls.
3. **Open/Closed (OCP):** extend by inheriting from `Control` without modifying existing code.
4. **Consistency:** unified `draw()` / `handleEvent()` across all controls.
## 🚀 Core Features
- **Extreme Lightweight**: tiny footprint with zero external dependencies beyond EasyX.
- **Clear Modular Architecture**: `CoreTypes.h` centralizes types and enums.
- **Native C++ Performance**: EasyX + Win32 API for near-native speed, low memory (<10MB typical).
- **Complete Control Set**: Button, Label, TextBox, Canvas, Table, Dialog, and MessageBox factory.
- **Highly Customizable**: colors, shapes (rectangle/rounded/circle/ellipse), fill modes, fonts via enums.
- **Simple, Intuitive API**: OOP-oriented, code as documentation.
- **Standard Project Layout**: include/src split, CMake build.
- **Enhanced Event System**: `handleEvent` returns `bool` for consumption (since v2.0.0).
- **Dialog System**: modal and modeless; auto background save/restore.
- **Ultra-lightweight:** no heavyweight external dependencies besides EasyX.
- **Clear modules:** `CoreTypes.h` unifies types and enums.
- **Native performance:** EasyX + Win32 for efficient execution and low memory (often <10 MB).
- **Complete control set:** Button, Label, TextBox, Canvas, Table, Dialog, MessageBox, **TabControl**.
- **Highly customizable:** colors; shapes (rectangle/rounded/circle/ellipse); fills; fonts—switchable via enums.
- **Simple, intuitive API:** OOP design with clear semantics—code as documentation.
- **Standard project layout:** split `include/src`, CMake-friendly, easy to integrate or use out of the box.
------
## ⚡ Quick Start (5 Minutes)
## ⚡ Quick Start (5 minutes)
> Download prebuilt headers/libs from [Releases](https://github.com/Ysm-04/StellarX/releases/latest).
> Get the prebuilt package from [Releases](https://github.com/Ysm-04/StellarX/releases/latest).
### Requirements
- **OS**: Windows 10+
- **Compiler**: C++17 (e.g., Visual Studio 2019+)
- **Graphics**: [EasyX](https://easyx.cn/) 2022+ (match your compiler)
- **Build**: CMake 3.12+ (optional)
- **OS:** Windows 10+
- **Compiler:** C++17 (e.g., VS 2019+)
- **Graphics:** [EasyX](https://easyx.cn/) 2022+ (matching your compiler)
- **Build:** CMake 3.12+ (optional)
### Install EasyX
1. Download latest EasyX
2. Install matching your Visual Studio
3. No extra configuration needed for StellarX
1. Download the latest EasyX
2. Install components matching your Visual Studio version
3. The framework links automatically—no extra config needed
### CMake Build (Recommended)
### Build with CMake (recommended)
```
git clone https://github.com/Ysm-04/StellarX.git
@@ -141,23 +131,22 @@ cmake --build .
### Manual Integration
- Copy `include` and `src` into your project
- Add include paths for `include/StellarX/`
- Add all `.cpp` files to your build
- Copy `include` and `src`
- Add header search path: `include/StellarX/`
- Add all `.cpp` files to your project
### Your First App (now Resizable)
### First Resizable Window
```
#include "StellarX.h"
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
Window mainWindow(800, 600, 0, RGB(255,255,255), "StellarX App");
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
// Resizing enabled by default; current size is the minimum size
Window mainWindow(800, 600, 0, RGB(255,255,255), "My StellarX App");
mainWindow.draw();
// Enable resize + minimum track size
mainWindow.enableResize(true, 480, 360);
// Add your controls here...
// Add your controls...
// mainWindow.addControl(std::move(btn));
mainWindow.runEventLoop();
@@ -165,160 +154,138 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
}
```
> Internally, we paint the **entire client area** in `WM_PAINT` (solid/image) and use EasyX batch drawing to avoid flicker and black borders.
> Implementation note: perform **full-window background drawing** (solid/image) during `WM_PAINT`, and combine with EasyX batch drawing to suppress flicker and black edges.
------
## 📚 Core Types (`CoreTypes.h`)
## 📚 Core Types (excerpt from `CoreTypes.h`)
### Enums (selection)
### Enums
| Enum | Description | Values |
| ------------------ | ----------- | ------------------------------------------------------------ |
| `ControlShape` | geometry | `RECTANGLE`, `B_RECTANGLE`, `ROUND_RECTANGLE`, `CIRCLE`, `ELLIPSE`, ... |
| `ButtonMode` | behavior | `NORMAL`, `TOGGLE`, `DISABLED` |
| `TextBoxMode` | modes | `INPUT_MODE`, `READONLY_MODE` |
| `FillMode` | fill | `SOLID`, `NULL`, `HATCHED`, ... |
| `FillStyle` | pattern | `HORIZONTAL`, `CROSS`, ... |
| `LineStyle` | border | `SOLID`, `DASH`, `DOT`, ... |
| `MessageBoxType` | dialog | `OK`, `OKCancel`, `YesNo`, ... |
| `MessageBoxResult` | result | `OK`, `Cancel`, `Yes`, `No`, `Abort`, `Retry`, `Ignore` |
| Enum | Description | Common values |
| ------------------ | ---------------- | ------------------------------------------------------------ |
| `ControlShape` | Geometric shape | `RECTANGLE`, `B_RECTANGLE`, `ROUND_RECTANGLE`, `CIRCLE`, `ELLIPSE` |
| `ButtonMode` | Button behavior | `NORMAL`, `TOGGLE`, `DISABLED` |
| `TextBoxMode` | TextBox mode | `INPUT_MODE`, `READONLY_MODE` |
| `FillMode` | Fill mode | `SOLID`, `NULL`, `HATCHED` |
| `FillStyle` | Pattern style | `HORIZONTAL`, `CROSS` |
| `LineStyle` | Line style | `SOLID`, `DASH`, `DOT` |
| `MessageBoxType` | Message box type | `OK`, `OKCancel`, `YesNo`, ... |
| `MessageBoxResult` | Result | `OK`, `Cancel`, `Yes`, `No`, `Abort`, `Retry`, `Ignore` |
| `TabPlacement` | Tab position | `Top`, `Bottom`, `Left`, `Right` |
### Structs
| Struct | Description |
| -------------- | -------------------------------------------- |
| `ControlText` | font/size/color/bold/italic/underline/strike |
| `RouRectangle` | rounded-rectangle corner ellipse size |
| -------------- | ---------------------------------------------------- |
| `ControlText` | Font/size/color/bold/italic/underline/strike-through |
| `RouRectangle` | Corner ellipse size for rounded rectangles |
------
## 🧩 Control Library
## 🧩 Controls Library
### 1) Basic
### 1) Basic Controls
| Control | Header | Description | Key Points |
| ------- | ----------- | ----------------------- | ------------------------------------------------------------ |
| Button | `Button.h` | multi-functional button | shapes/modes, hover/click colors, callbacks, **single-line truncation** + **tooltip** (v2.1.0) |
| Label | `Label.h` | text label | transparent/opaque backgrounds, custom font styles |
| TextBox | `TextBox.h` | input/display | input & read-only, integrates EasyX `InputBox` |
| ------- | ----------- | ----------------- | ------------------------------------------------------------ |
| Button | `Button.h` | Versatile button | Shapes/modes; hover/pressed colors; callbacks; **single-line truncation** + **Tooltip** (v2.1.0) |
| Label | `Label.h` | Text label | Transparent/opaque background; custom fonts |
| TextBox | `TextBox.h` | Input/display box | Input/readonly; integrates EasyX `InputBox` |
### 2) Containers
### 2) Container Controls
| Control | Header | Description |
| ------- | ---------- | ------------------------------------------------------------ |
| Canvas | `Canvas.h` | parent container, custom border/background, **auto layout HBox/VBox** (v2.1.0) |
| Window | `Window.h` | top-level container, message loop/dispatch, **resizable** (v2.1.0) |
| Canvas | `Canvas.h` | Parent container with custom border/background; **built-in HBox/VBox auto layout** (v2.1.0) |
| Window | `Window.h` | Top-level container with message loop and dispatch; **resizable** (v2.1.0) |
### 3) Advanced
### 3) Advanced Controls
| Control | Header | Description | Key Points |
| ------- | ---------- | ----------- | ------------------------------------------------------------ |
| Table | `Table.h` | data table | pagination, header/data, auto column widths, page controls fixed overlap/centering (v2.1.0) |
| Dialog | `Dialog.h` | dialogs | modal/modeless, layout & background save/restore |
| ---------- | -------------- | ----------- | ------------------------------------------------------------ |
| Table | `Table.h` | Data grid | Paging/header/auto column width; fixed page-control overlap/ghosting (v2.1.0) |
| Dialog | `Dialog.h` | Dialog | Modal/non-modal; auto layout; background save/restore |
| TabControl | `TabControl.h` | Tabs | One-click add of “tab + page” pair (pair), or add child controls to a page; uses relative coordinates |
### 4) Static Factory
| Control | Header | Description | Key Points |
| ---------- | -------------- | -------------- | ------------------------------------------- |
| MessageBox | `MessageBox.h` | dialog factory | static API; modal/modeless; dedup mechanism |
| ---------- | -------------- | ------------------- | -------------------------------------------- |
| MessageBox | `MessageBox.h` | Message-box factory | Static API; modal/non-modal; de-dup built in |
------
## 📐 Layout Management (HBox/VBox)
- **Container-level**: `Canvas::layout.kind = HBox / VBox / Absolute (default)`; optional container `padding`.
- **Child-level**: `LayoutParams`
- `margin{L,R,T,B}`
- `fixedW/fixedH` (`-1` = keep current)
- `weight` (distribute along main axis; width in HBox, height in VBox)
- `alignX/alignY` = `Start/Center/End/Stretch` (cross-axis)
**Example** (3 items HBox, center vertically, stretch height):
```
Canvas row(20, 20, 760, 120);
row.layout.kind = LayoutKind::HBox;
auto mk = [](int w, int h, float weight){
auto c = std::make_unique<Control>(0,0,w,h);
c->layout.marginL = 8; c->layout.marginR = 8;
c->layout.alignY = LayoutParams::Stretch;
c->layout.weight = weight;
return c;
};
row.addControl(mk(100,40, 1.f));
row.addControl(mk(100,40, 2.f));
row.addControl(mk(100,40, 1.f));
```
==Reserved, to be implemented==
------
## 🗂 Tabs (Early)
## 🗂 Tabs (TabControl)
- Tab strip (buttons) + page container (`Canvas`)
- Transparent themes: page area uses **background snapshot** on switch
- API sketch: `tabs.addPage(title, canvasPtr); tabs.setActive(index);`
- Tab strip (button group) + page container (`Canvas`)
- For transparent themes: **background snapshot** switching in the page area to avoid ghosting
- API: **see the API documentation**
------
## ✂️ Single-Line Truncation & 🫧 Tooltip
## ✂️ Single-line Text Truncation & Button Tooltip
- **Button truncation**: MBCS CN/EN aware pixel-width threshold + `...`
- **Tooltip**: delayed show, auto-hide; default text = button label; customizable; uses per-control **background snapshot/restore**.
- **Button truncation:** separate handling for CJK/Latin under MBCS; append `...` based on pixel-width threshold
- **Tooltip:** delayed show and auto-hide; default text = button text; customizable; uses control-level **background snapshot/restore**
------
## 🧊 Transparent Background & Background Snapshot
## 🧊 Transparent Background & Background Snapshots
- **Convention**: `captureBackground(rect)` before first draw; `restoreBackground()` before hiding/overdraw.
- **Table**: snapshot area now **includes header**; pagination re-centers controls and restores backgrounds immediately.
- **General convention:** `captureBackground(rect)` before the first draw; `restoreBackground()` before hiding/covering
- **Table:** snapshot region **includes the header**; after page switch, restore immediately + redraw; paging controls centered
------
## 🔧 Advanced Topics & Best Practices
- Custom controls: inherit `Control` and implement `draw()` / `handleEvent()`.
- Custom controls: inherit from `Control`, implement `draw()` / `handleEvent()`
- Performance:
- **Dirty rendering**: set `dirty=true` when state changes; framework repaints as needed.
- **Avoid extra `cleardevice()`**: background is already handled in `WM_PAINT` full-surface path.
- Use `SetWorkingImage(nullptr)` before drawing to the screen.
- Event consumption: return `true` if handled to stop propagation.
- **Dirty rectangles:** set `dirty=true` on state changes for on-demand redraw
- **Avoid extra `cleardevice()`**: background is centrally handled in `WM_PAINT`
- Ensure `SetWorkingImage(nullptr)` before drawing so output goes to the screen
- Event consumption: return `true` after handling to stop propagation
------
## ⚠️ Limitations & When Not To Use
## ⚠️ Applicability & Limits
- High-performance games or complex animations
- High DPI scaling extremes (re-verify coordinates/metrics)
- Accessibility requirements
- Cross-platform targets (Windows-only)
- Heavy enterprise frontends (consider Qt/wxWidgets/ImGui/Electron)
- Not suitable for high-performance games or complex animation; re-verify metrics under extreme DPI
- No accessibility support yet
- Windows-only, not cross-platform
- For complex commercial front-ends, consider Qt / wxWidgets / ImGui / Electron
------
## 📜 License
MIT License (see `LICENSE`).
MIT (see `LICENSE`).
## 👥 Contributing
## 👥 Contributing Guidelines
- Follow the existing C++ style (spaces, brace on new line).
- New features should include an example and README update.
- Test before PR; describe changes and motivation.
- Use Issues for bugs/ideas.
- Follow the existing C++ style
- New features should include examples and README updates
- Self-test before submitting and explain the motivation for changes
- For bugs/ideas, please open an Issue
## 🙏 Acknowledgements
- Thanks to [EasyX](https://easyx.cn/).
- Thanks to developers who value **concise, efficient, clear** code.
- Thanks to [EasyX](https://easyx.cn/)
- Thanks to developers who value **simplicity/efficiency/clarity**
------
**Star Ocean Vast, Code as Boat.**
**Stars and seas, code as the vessel.**
## 📞 Support & Feedback
- See [examples/](examples/)
- See [Changelog](CHANGELOG.en.md)
- Open Issues on GitHub
- Read the [CHANGELOG](CHANGELOG.md / CHANGELOG.en.md)
- Submit an Issue on GitHub

View File

@@ -7,8 +7,8 @@
![GitHub all releases](https://img.shields.io/github/downloads/Ysm-04/StellarX/total)
[![Star GitHub Repo](https://img.shields.io/github/stars/Ysm-04/StellarX.svg?style=social&label=Star%20This%20Repo)](https://github.com/Ysm-04/StellarX)
![Version](https://img.shields.io/badge/Version-2.1.0-brightgreen.svg)
![Download](https://img.shields.io/badge/Download-2.1.0_Release-blue.svg)
![Version](https://img.shields.io/badge/Version-2.2.0-brightgreen.svg)
![Download](https://img.shields.io/badge/Download-2.2.0_Release-blue.svg)
![C++](https://img.shields.io/badge/C++-17+-00599C?logo=cplusplus&logoColor=white)
![Windows](https://img.shields.io/badge/Platform-Windows-0078D6?logo=windows)
@@ -25,24 +25,14 @@
---
## 🆕 v2.1.0 有何变化
## 🆕 v2.2.0 有何变化
- **中英文双语 API文档**
- 文档详细介绍了每个类以及API描述、功能和需要注意的地方详细介绍了每个控件
- **新增 TabControl 控件,实现多页面选项卡界面:** 通过 `TabControl` 可以轻松创建选项卡式布局,支持页签在上下左右排列、点击切换显示不同内容页面。适用于设置面板、多视图切换等场景。
- **控件显隐与布局响应能力增强:** 现在所有控件都可以使用统一接口动态隐藏或显示(`setIsVisible`),容器控件隐藏时其内部子控件会自动随之隐藏/显示。与此同时,引入控件对窗口尺寸变化的响应机制(`onWindowResize`),窗口拉伸后界面各元素可协调更新,杜绝拉伸过程中出现残影或错位。
- **文本样式机制完善:** Label 控件改用统一的文本样式结构 `ControlText`,开发者可方便地设置字体、颜色、大小等属性来定制 Label 的外观替代旧接口更加灵活。Button 的 Tooltip 提示也支持更丰富的定制和针对切换状态的不同提示文本。
- **其他改进:** 框架底层的对话框管理增加了防重复弹出相同提示的机制,修复了一些细节 Bug 并优化了刷新效率,进一步提升了稳定性。
- **窗口可拉伸/最大化补强(基于 EasyX + Win32**
子类化窗口过程,处理 `WM_GETMINMAXINFO / WM_SIZE / WM_EXITSIZEMOVE / WM_ERASEBKGND / WM_PAINT`,启用 `WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CLIPCHILDREN | WS_CLIPSIBLINGS`。在 `WM_PAINT` 期进行**整窗背景绘制**(纯色/图片),彻底消除黑边与最大化“残影”;合并拉伸为**一次性重绘**。
- **布局管理器(第一阶段)**
`Canvas` 支持 `LayoutKind::{Absolute, HBox, VBox, Grid(预留), Flow(预留), Stack(预留)}``LayoutParams`(边距、固定尺寸、`weight``Align{Start,Center,End,Stretch}`);实装 **HBox/VBox** 自动布局(容器仍绝对定位,可嵌套)。
- **选项卡Tabs雏形**
页签条与页面容器分离;透明主题下使用**背景快照**避免叠影。
- **按钮文本单行截断MBCS中/英分治)**
基于像素宽度阈值的 `...` 截断,杜绝半字符。
- **悬停提示Tooltip**
`Label` 实现,支持延时出现、自动隐藏;控件级**背景快照/恢复**。
详见 `CHANGELOG.md / CHANGELOG.en.md`
详见 `CHANGELOG.md / CHANGELOG.en.md` 获取完整更新列表。
---
@@ -61,9 +51,10 @@ StellarX/
│ ├── Window.h
│ ├── Label.h
│ ├── TextBox.h
│ ├── TabControl.h #v2.2.0
│ ├── Canvas.h
│ ├── Dialog.h # v2.0.0
│ ├── MessageBox.h # v2.0.0
│ ├── Dialog.h
│ ├── MessageBox.h
│ └── Table.h
├── src/
│ ├── Control.cpp
@@ -72,9 +63,10 @@ StellarX/
│ ├── Label.cpp
│ ├── TextBox.cpp
│ ├── Canvas.cpp
│ ├── TabControl.cpp #v2.2.0
│ ├── Table.cpp
│ ├── Dialog.cpp # v2.0.0
│ └── MessageBox.cpp # v2.0.0
│ ├── Dialog.cpp
│ └── MessageBox.cpp
├── examples/
│ └── demo.cpp
├── docs/
@@ -102,12 +94,10 @@ StellarX/
- **极致轻量**:除 EasyX 外无外部重量级依赖。
- **模块清晰**`CoreTypes.h` 统一类型与枚举。
- **原生性能**EasyX + Win32执行高效、内存低占用常见 <10MB
- **控件齐全**Button、Label、TextBox、Canvas、Table、Dialog、MessageBox。
- **高度自定义**:颜色、形状(矩形/圆角/圆/椭圆)、填充、字体等皆有枚举。
- **简单直观 API**OOP 设计,代码即文档。
- **标准工程结构**include/src 分离,支持 CMake。
- **增强事件系统**`handleEvent` 返回是否消费(自 v2.0.0)。
- **对话框系统**:支持模态/非模态,自动背景保存与恢复。
- **控件齐全**Button、Label、TextBox、Canvas、Table、Dialog、MessageBox、**TabControl**
- **高度自定义**:颜色、形状(矩形/圆角/圆/椭圆)、填充、字体等皆有枚举配置,易于切换
- **简单直观 API**OOP 设计,接口语义明确、调用友好,代码即文档。
- **标准工程结构**include/src 分离,支持 CMake 构建,方便集成到现有项目或开箱即用
------
@@ -130,7 +120,7 @@ StellarX/
### CMake 构建(推荐)
```
```bash
git clone https://github.com/Ysm-04/StellarX.git
cd StellarX
mkdir build && cd build
@@ -182,6 +172,7 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
| `LineStyle` | 线型 | `SOLID`, `DASH`, `DOT` |
| `MessageBoxType` | 消息框类型 | `OK`, `OKCancel`, `YesNo`, ... |
| `MessageBoxResult` | 结果 | `OK`, `Cancel`, `Yes`, `No`, `Abort`, `Retry`, `Ignore` |
| `TabPlacement` | 页签位置 | `Top`,`Bottom`,`Left`,`Right` |
### 结构体
@@ -212,9 +203,10 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
### 3) 高级控件
| 控件 | 头文件 | 描述 | 关键点 |
| ------ | ---------- | -------- | --------------------------------------------------------- |
| ---------- | -------------- | -------- | ------------------------------------------------------------ |
| Table | `Table.h` | 数据表格 | 分页/表头/列宽自动、翻页控件重叠/透明叠影已修复v2.1.0 |
| Dialog | `Dialog.h` | 对话框 | 支持模态/非模态,自动布局与背景保存/恢复 |
| TabControl | `TabControl.h` | 选项卡 | 支持一键添加“页签+页”对pair也可以单独对某页添加子控件采用相对坐标 |
### 4) 静态工厂
@@ -226,42 +218,21 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
## 📐 布局管理HBox/VBox
- **容器级**`Canvas::layout.kind = HBox / VBox / Absolute (默认)`;可选容器 `padding`。
- **子项级**`LayoutParams`
- `margin{L,R,T,B}`
- `fixedW/fixedH``-1` 表示沿用控件当前尺寸)
- `weight`主轴分配权重HBox→宽、VBox→高
- `alignX/alignY` = `Start/Center/End/Stretch`(用于次轴方向对齐/拉伸)
==预留,待实现==
**示例**(三列水平自适应,整体垂直拉伸):
```c++
Canvas row(20, 20, 760, 120);
row.layout.kind = LayoutKind::HBox;
auto mk = [](int w, int h, float weight){
auto c = std::make_unique<Control>(0,0,w,h);
c->layout.marginL = 8; c->layout.marginR = 8;
c->layout.alignY = LayoutParams::Stretch;
c->layout.weight = weight;
return c;
};
row.addControl(mk(100,40, 1.f));
row.addControl(mk(100,40, 2.f));
row.addControl(mk(100,40, 1.f));
```
------
## 🗂 选项卡Tabs早期版本
## 🗂 选项卡TabControl
- 页签条(按钮组) + 页面容器(`Canvas`
- 透明主题:页面区域**背景快照**切换,避免叠影
- API 草图:`tabs.addPage(title, canvasPtr); tabs.setActive(index);`
- API **查看API文档**
------
## ✂️ 文本单行截断 & 🫧 Tooltip
## ✂️ 文本单行截断 & Button Tooltip
- **按钮截断**:多字节字符集下**中/英分治**,基于像素宽度阈值追加 `...`
- **Tooltip**:延时出现、自动隐藏;默认文字=按钮文本,可自定义;使用控件级**背景快照/恢复**

View File

@@ -171,20 +171,10 @@ public:
//设置提示框位置偏移
void setTooltipOffset(int dx, int dy) { tipOffsetX = dx; tipOffsetY = dy; }
//设置提示框样式
void setTooltipStyle(COLORREF text, COLORREF bk, bool transparent)
{
tipLabel.setTextColor(text);
tipLabel.setTextBkColor(bk);
tipLabel.setTextdisap(transparent);
}
void setTooltipStyle(COLORREF text, COLORREF bk, bool transparent);
//设置提示框文本
void setTooltipText(const std::string& s){ tipTextClick = s; tipUserOverride = true; }
void setTooltipTextsForToggle(const std::string& onText, const std::string& offText)
{
tipTextOn = onText;
tipTextOff = offText;
tipUserOverride = true;
}
void setTooltipTextsForToggle(const std::string& onText, const std::string& offText);
private:
//初始化按钮
void initButton(const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape, COLORREF ct, COLORREF cf, COLORREF ch);

View File

@@ -56,7 +56,12 @@ public:
void setCanvasLineStyle(StellarX::LineStyle style);
//设置线段宽度
void setLinewidth(int width);
//设置不可见后传递给子控件重写
void setIsVisible(bool visible) override;
void setDirty(bool dirty) override;
void onWindowResize() override;
//获取子控件列表
std::vector<std::unique_ptr<Control>>& getControls() { return controls; }
private:
//用来检查对话框是否模态,此控件不做实现
bool model() const override { return false; };

View File

@@ -34,11 +34,14 @@
#include <string>
#include <functional>
#include "CoreTypes.h"
class Control
{
protected:
int x, y; // 左上角坐标
int width, height; // 控件尺寸
std::string id; // 控件ID
int localx,x, localy,y; // 左上角坐标
int localWidth,width, localHeight,height; // 控件尺寸
Control* parent = nullptr; // 父控件
bool dirty = true; // 是否重绘
bool show = true; // 是否显示
@@ -61,9 +64,9 @@ protected:
Control(Control&&) = default;
Control& operator=(Control&&) = default;
Control() : x(0), y(0), width(100), height(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)
: x(x), y(y), width(width), height(height) {}
: localx(x), x(x), localy(y), y(y), localWidth(width), width(width), height(height), localHeight(height){}
public:
virtual ~Control()
@@ -79,15 +82,24 @@ public:
currentBkColor = nullptr;
currentBorderColor = nullptr;
currentLineStyle = nullptr;
discardBackground();
}
protected:
virtual void requestRepaint();
//根控件/无父时触发重绘
virtual void onRequestRepaintAsRoot();
protected:
//保存背景快照
void saveBackground(int x, int y, int w, int h);
void restBackground(); // putimage 回屏
void discardBackground(); // 释放快照(窗口重绘/尺寸变化后必须作废)
// putimage 回屏
void restBackground();
// 释放快照(窗口重绘/尺寸变化后必须作废)
void discardBackground();
public:
//释放快照重新保存,在尺寸变化时更新背景快照避免尺寸变化导致显示错位
void updateBackground();
//窗口变化丢快照
virtual void onWindowResize();
// 获取位置和尺寸
int getX() const { return x; }
int getY() const { return y; }
@@ -96,22 +108,34 @@ public:
int getRight() const { return x + width; }
int getBottom() const { return y + height; }
int getLocalX() const { return localx; }
int getLocalY() const { return localy; }
int getLocalWidth() const { return localWidth; }
int getLocalHeight() const { return localHeight; }
int getLocalRight() const { return localx + localWidth; }
int getLocalBottom() const { return localy + localHeight; }
void setX(int x) { this->x = x; dirty = true; }
void setY(int y) { this->y = y; dirty = true; }
void setWidth(int width) { this->width = width; dirty = true; }
void setHeight(int height) { this->height = height; dirty = true; }
public:
//设置是否重绘
void setDirty(bool dirty) { this->dirty = dirty; }
virtual void draw() = 0;
virtual bool handleEvent(const ExMessage& msg) = 0;//返回true代表事件已消费
//设置是否显示
void setShow(bool show) { this->show = show; }
virtual void setIsVisible(bool show);
//设置父容器指针
void setParent(Control* parent) { this->parent = parent; }
//设置是否重绘
virtual void setDirty(bool dirty) { this->dirty = dirty; }
//检查控件是否可见
bool IsVisible() const { return show; };
//获取控件id
std::string getId() const { return id; }
//检查是否为脏
bool isDirty() { return dirty; }
//用来检查对话框是否模态,其他控件不用实现
virtual bool model()const = 0;
protected:

View File

@@ -319,4 +319,28 @@ namespace StellarX
int flowBreak = 0; // 1=强制换行
};
#endif
/*
* @枚举名称: TabPlacement
* @功能描述: 定义了选项卡页签的不同位置
*
* @详细说明:
* 根据选项卡页签的位置,选项卡页签可以有不同的布局方式。
*
* @成员说明:
* Top, - 选项卡页签位于顶部
* Bottom, - 选项卡页签位于底部
* Left, - 选项卡页签位于左侧
* Right - 选项卡页签位于右侧
*
* @使用示例:
* TabPlacement placement = TabPlacement::Top;
*/
enum class TabPlacement
{
Top,
Bottom,
Left,
Right
};
}

View File

@@ -1,7 +1,7 @@
/*******************************************************************************
* @文件: StellarX.h
* @摘要: 星垣(StellarX) GUI框架 - 主包含头文件
* @版本: v2.0.0
* @版本: v2.2.0
* @描述:
* 一个为Windows平台打造的轻量级、模块化C++ GUI框架。
* 基于EasyX图形库提供简洁易用的API和丰富的控件。
@@ -39,3 +39,4 @@
#include"Table.h"
#include"Dialog.h"
#include"MessageBox.h"
#include"TabControl.h"

View File

@@ -0,0 +1,66 @@
/*******************************************************************************
* @类: TabControl
* @摘要: 选项卡容器控件,管理“页签按钮 + 对应页面(Canvas)”
* @描述:
* 提供页签栏布局(上/下/左/右)、选中切换、页内容区域定位;
* 与 Button 一起工作,支持窗口大小变化、可见性联动与脏区重绘。
*
* @特性:
* - 页签栏四向排列Top / Bottom / Left / Right
* - 一键添加“页签+页”或为指定页添加子控件
* - 获取/设置当前激活页签索引
* - 自适应窗口变化,重算页签与页面区域
* - 与 Button 的 TOGGLE 模式联动显示/隐藏页面
*
* @使用场景: 在同一区域内承载多张页面,使用页签进行快速切换
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
******************************************************************************/
#pragma once
#include "CoreTypes.h"
#include "Button.h"
#include "Canvas.h"
#define BUTMINHEIGHT 15
#define BUTMINWIDTH 30
class TabControl :public Canvas
{
int tabBarHeight = BUTMINWIDTH; //页签栏高度
StellarX::TabPlacement tabPlacement = StellarX::TabPlacement::Top ; //页签排列方式
std::vector<std::pair<std::unique_ptr<Button>,std::unique_ptr<Canvas>>> controls; //页签/页列表
private:
using Canvas::addControl; // 禁止外部误用
void addControl(std::unique_ptr<Control>) = delete; // 精准禁用该重载
private:
inline void initTabBar();
inline void initTabPage();
public:
TabControl();
TabControl(int x,int y,int width,int height);
~TabControl();
void draw() override;
bool handleEvent(const ExMessage& msg) override;
//添加页签+页
void add(std::pair<std::unique_ptr<Button> ,std::unique_ptr<Canvas>>&& control);
//添加为某个页添加控件
void add(std::string tabText,std::unique_ptr<Control> control);
//设置页签位置
void setTabPlacement(StellarX::TabPlacement placement);
//设置页签栏高度 两侧排列时为宽度
void setTabBarHeight(int height);
//设置不可见后传递给子控件重写
void setIsVisible(bool visible) override;
void onWindowResize() override;
//获取当前激活页签索引
int getActiveIndex() const;
//设置当前激活页签索引
void setActiveIndex(int idx);
//获取页签数量
int count() const;
//通过页签文本返回索引
int indexOf(const std::string& tabText) const;
};

View File

@@ -22,7 +22,6 @@
class Label : public Control
{
std::string text; //标签文本
COLORREF textColor; //标签文本颜色
COLORREF textBkColor; //标签背景颜色
bool textBkDisap = false; //标签背景是否透明
@@ -42,8 +41,6 @@ public:
void hide();
//设置标签背景是否透明
void setTextdisap(bool key);
//设置标签文本颜色
void setTextColor(COLORREF color);
//设置标签背景颜色
void setTextBkColor(COLORREF color);
//设置标签文本

View File

@@ -22,6 +22,34 @@
#include "Button.h"
#include "Label.h"
// === Table metrics (layout) ===
#define TABLE_PAD_X 10 // 单元格左右内边距
#define TABLE_PAD_Y 5 // 单元格上下内边距
#define TABLE_COL_GAP 20 // 列间距(列与列之间)
#define TABLE_HEADER_EXTRA 10 // 表头额外高度(若不想复用 pad 计算)
#define TABLE_ROW_EXTRA 10 // 行额外高度(同上;或直接用 2*TABLE_PAD_Y
#define TABLE_BTN_GAP 12 // 页码与按钮的水平间距
#define TABLE_BTN_PAD_H 12 // 按钮水平 padding
#define TABLE_BTN_PAD_V 0 // 按钮垂直 paddinginitButton
#define TABLE_BTN_TEXT_PAD_V 8 // 计算页脚高度时的按钮文字垂直 paddinginitTextWaH
#define TABLE_FOOTER_PAD 16 // 页脚额外高度(底部留白)
#define TABLE_FOOTER_BLANK 8 // 页脚顶部留白
#define TABLE_PAGE_TEXT_OFFSET_X (-40) // 页码文本的临时水平修正
// === Table defaults (theme) ===
#define TABLE_DEFAULT_ROWS_PER_PAGE 5
#define TABLE_DEFAULT_BORDER_WIDTH 1
#define TABLE_DEFAULT_BORDER_COLOR RGB(0,0,0)
#define TABLE_DEFAULT_BG_COLOR RGB(255,255,255)
// === Strings (i18n ready) ===
#define TABLE_STR_PREV "上一页"
#define TABLE_STR_NEXT "下一页"
#define TABLE_STR_PAGE_PREFIX "第"
#define TABLE_STR_PAGE_MID "页/共"
#define TABLE_STR_PAGE_SUFFIX "页"
class Table :public Control
{
private:
@@ -29,12 +57,12 @@ private:
std::vector<std::string> headers; // 表格表头
std::string pageNumtext = "页码标签"; // 页码标签文本
int tableBorderWidth = 1; // 边框宽度
int tableBorderWidth = TABLE_DEFAULT_BORDER_WIDTH; // 边框宽度
std::vector<int> colWidths; // 每列的宽度
std::vector<int> lineHeights; // 每行的高度
int rowsPerPage = 5; // 每页显示的行数
int rowsPerPage = TABLE_DEFAULT_ROWS_PER_PAGE; // 每页显示的行数
int currentPage = 1; // 当前页码
int totalPages = 1; // 总页数
@@ -54,8 +82,8 @@ private:
StellarX::FillMode tableFillMode = StellarX::FillMode::Solid; //填充模式
StellarX::LineStyle tableLineStyle = StellarX::LineStyle::Solid; // 线型
COLORREF tableBorderClor = RGB(0, 0, 0); // 表格边框颜色
COLORREF tableBkClor = RGB(255, 255, 255); // 表格背景颜色
COLORREF tableBorderClor = TABLE_DEFAULT_BORDER_COLOR; // 表格边框颜色
COLORREF tableBkClor = TABLE_DEFAULT_BG_COLOR; // 表格背景颜色
void initTextWaH(); //初始化文本像素宽度和高度
void initButton(); //初始化翻页按钮

View File

@@ -128,8 +128,23 @@ static std::string ellipsize_cjk_pref(const std::string& text, int maxW, const c
return head;
}
void Button::setTooltipStyle(COLORREF text, COLORREF bk, bool transparent)
{
tipLabel.textStyle.color = text;
tipLabel.setTextBkColor(bk);
tipLabel.setTextdisap(transparent);
}
void Button::setTooltipTextsForToggle(const std::string& onText, const std::string& offText)
{
tipTextOn = onText;
tipTextOff = offText;
tipUserOverride = true;
}
void Button::initButton(const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape, COLORREF ct, COLORREF cf, COLORREF ch)
{
this->id = "Button";
this->text = text;
this->mode = mode;
this->shape = shape;
@@ -142,7 +157,7 @@ void Button::initButton(const std::string text, StellarX::ButtonMode mode, Stell
// === Tooltip 默认:文本=按钮文本;白底黑字;不透明;用当前按钮字体样式 ===
tipTextClick = tipTextOn = tipTextOff = this->text;
tipLabel.setText(tipTextClick);
tipLabel.setTextColor(RGB(167, 170, 172));
tipLabel.textStyle.color = (RGB(167, 170, 172));
tipLabel.setTextBkColor(RGB(255, 255, 255));
tipLabel.setTextdisap(false);
tipLabel.textStyle = this->textStyle; // 复用按钮字体样式
@@ -158,8 +173,8 @@ Button::~Button()
void Button::draw()
{
if (dirty && show)
{
if (!dirty || !show)return;
//保存当前样式和颜色
saveStyle();
@@ -208,6 +223,10 @@ void Button::draw()
//设置按钮填充模式
setfillstyle((int)buttonFillMode, (int)buttonFillIma, buttonFileIMAGE);
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
saveBackground(this->x, this->y, this->width, this->height);
// 恢复背景(清除旧内容)
restBackground();
//根据按钮形状绘制
switch (shape)
{
@@ -256,7 +275,7 @@ void Button::draw()
restoreStyle();//恢复默认字体样式和颜色
dirty = false; //标记按钮不需要重绘
}
}
// 处理鼠标事件,检测点击和悬停状态
// 根据按钮模式和形状进行不同的处理
@@ -398,7 +417,7 @@ bool Button::handleEvent(const ExMessage& msg)
// 如果需要重绘,立即执行
if (dirty)
draw();
requestRepaint();
if(tipEnabled && tipVisible)
tipLabel.draw();
return consume;
@@ -420,8 +439,11 @@ void Button::setOnToggleOffListener(const std::function<void()>&& callback)
void Button::setbuttonMode(StellarX::ButtonMode mode)
{
if (this->mode == StellarX::ButtonMode::DISABLED && mode != StellarX::ButtonMode::DISABLED)
textStyle.bStrikeOut = false;
//取值范围参考 buttMode的枚举注释
this->mode = mode;
dirty = true; // 标记需要重绘
}
void Button::setROUND_RECTANGLEwidth(int width)
@@ -532,7 +554,7 @@ void Button::setButtonClick(BOOL click)
flushmessage(EX_MOUSE | EX_KEY);
}
if (dirty)
draw();
requestRepaint();
}

View File

@@ -1,10 +1,16 @@
#include "Canvas.h"
Canvas::Canvas()
:Control(0, 0, 100, 100) {}
:Control(0, 0, 100, 100)
{
this->id = "Canvas";
}
Canvas::Canvas(int x, int y, int width, int height)
:Control(x, y, width, height) {}
:Control(x, y, width, height)
{
this->id = "Canvas";
}
void Canvas::clearAllControls()
{
@@ -14,14 +20,17 @@ void Canvas::clearAllControls()
void Canvas::draw()
{
if (!dirty && !show)return;
if (!dirty||!show)return;
saveStyle();
setlinecolor(canvasBorderClor);//设置线色
setfillcolor(canvasBkClor);//设置填充色
setfillstyle((int)canvasFillMode);//设置填充模式
setlinestyle((int)canvasLineStyle, canvaslinewidth);
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
saveBackground(this->x, this->y, this->width, this->height);
// 恢复背景(清除旧内容)
restBackground();
//根据画布形状绘制
switch (shape)
{
@@ -52,15 +61,21 @@ void Canvas::draw()
bool Canvas::handleEvent(const ExMessage& msg)
{
if (!show)return false;
bool consumed = false;
bool anyDirty = false;
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
if ((*it)->handleEvent(msg))
return true; // 事件被消费短路传递立即返回true 否则返回false
return false;
{
consumed |= it->get()->handleEvent(msg);
if (it->get()->isDirty()) anyDirty = true;
}
if (anyDirty) requestRepaint();
return consumed;
}
void Canvas::addControl(std::unique_ptr<Control> control)
{
control->setParent(this);
controls.push_back(std::move(control));
dirty = true;
}
@@ -117,5 +132,32 @@ void Canvas::setLinewidth(int width)
dirty = true;
}
void Canvas::setIsVisible(bool visible)
{
this->show = visible;
dirty = true;
for (auto& control : controls)
{
control->setIsVisible(visible);
control->setDirty(true);
}
if (!visible)
this->updateBackground();
}
void Canvas::setDirty(bool dirty)
{
this->dirty = dirty;
for(auto& control : controls)
control->setDirty(dirty);
}
void Canvas::onWindowResize()
{
Control::onWindowResize(); // 先处理自己
for (auto& ch : controls) // 再转发给所有子控件
ch->onWindowResize();
}

View File

@@ -42,6 +42,18 @@ bool StellarX::ControlText::operator!=(const ControlText& text)
return true;
return false;
}
void Control::setIsVisible(bool show)
{
if (!show)
this->updateBackground();
this->show = show;
}
void Control::onWindowResize()
{
// 自己:丢快照 + 标脏
discardBackground();
setDirty(true);
}
// 保存当前的绘图状态(字体、颜色、线型等)
// 在控件绘制前调用,确保不会影响全局绘图状态
void Control::saveStyle()
@@ -65,6 +77,20 @@ void Control::restoreStyle()
setfillstyle(BS_SOLID);//恢复填充
}
void Control::requestRepaint()
{
if (parent) parent->requestRepaint(); // 向上冒泡
else onRequestRepaintAsRoot(); // 到根控件/窗口兜底
}
void Control::onRequestRepaintAsRoot()
{
discardBackground();
setDirty(true);
draw(); // 只有“无父”时才允许立即画,不会被谁覆盖
}
void Control::saveBackground(int x, int y, int w, int h)
{
if (w <= 0 || h <= 0) return;
@@ -105,10 +131,6 @@ void Control::discardBackground()
void Control::updateBackground()
{
if (saveBkImage)
{
delete saveBkImage;
saveBkImage = nullptr;
}
hasSnap = false; saveWidth = saveHeight = 0;
restBackground();
discardBackground();
}

View File

@@ -3,6 +3,7 @@
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)
{
this->id = "Dialog";
show = false;
}
@@ -36,9 +37,8 @@ void Dialog::draw()
saveStyle();
// 保存背景(仅在第一次绘制时)
if (saveBkImage == nullptr)
saveBackground((x - BorderWidth), (y - BorderWidth), (width + 2 * BorderWidth), (height + 2 * BorderWidth));
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
saveBackground(this->x, this->y, this->width, this->height);
Canvas::setBorderColor(this->borderColor);
Canvas::setLinewidth(this->BorderWidth);
@@ -191,7 +191,7 @@ void Dialog::Show()
// 重绘
if (dirty)
{
draw();
requestRepaint();
FlushBatchDraw();
}

350
src/TabControl.cpp Normal file
View File

@@ -0,0 +1,350 @@
#include "TabControl.h"
inline void TabControl::initTabBar()
{
if (controls.empty())return;
int butW = max(this->width / (int)controls.size(), BUTMINWIDTH);
int butH = max(this->height / (int)controls.size(), BUTMINHEIGHT);
if (this->tabPlacement == StellarX::TabPlacement::Top || this->tabPlacement == StellarX::TabPlacement::Bottom)
for (auto& c : controls)
{
c.first->setHeight(tabBarHeight);
c.first->setWidth(butW);
}
else if (this->tabPlacement == StellarX::TabPlacement::Left || this->tabPlacement == StellarX::TabPlacement::Right)
for (auto& c : controls)
{
c.first->setHeight(butH);
c.first->setWidth(tabBarHeight);
}
int i = 0;
switch (this->tabPlacement)
{
case StellarX::TabPlacement::Top:
for (auto& c : controls)
{
c.first->setX(this->x + i * butW);
c.first->setY(this->y);
i++;
}
break;
case StellarX::TabPlacement::Bottom:
for (auto& c : controls)
{
c.first->setX(this->x + i * butW);
c.first->setY(this->y+this->height - tabBarHeight);
i++;
}
break;
case StellarX::TabPlacement::Left:
for (auto& c : controls)
{
c.first->setX(this->x);
c.first->setY(this->y+i* butH);
i++;
}
break;
case StellarX::TabPlacement::Right:
for (auto& c : controls)
{
c.first->setX(this->x+this->width - tabBarHeight);
c.first->setY(this->y + i * butH);
i++;
}
break;
default:
break;
}
}
inline void TabControl::initTabPage()
{
if (controls.empty())return;
//子控件坐标原点
int nX = 0;
int nY = 0;
switch (this->tabPlacement)
{
case StellarX::TabPlacement::Top:
for (auto& c : controls)
{
c.second->setX(this->x);
c.second->setY(this->y + tabBarHeight);
c.second->setWidth(this->width);
c.second->setHeight(this->height - tabBarHeight);
}
nX = this->x;
nY = this->y + tabBarHeight;
for (auto& c : controls)
{
for (auto& v : c.second->getControls())
{
v->setX(v->getLocalX() + nX);
v->setY(v->getLocalY() + nY);
}
}
break;
case StellarX::TabPlacement::Bottom:
for (auto& c : controls)
{
c.second->setX(this->x);
c.second->setY(this->y);
c.second->setWidth(this->width);
c.second->setHeight(this->height - tabBarHeight);
}
nX = this->x;
nY = this->y;
for (auto& c : controls)
{
for (auto& v : c.second->getControls())
{
v->setX(v->getLocalX() + nX);
v->setY(v->getLocalY() + nY);
}
}
break;
case StellarX::TabPlacement::Left:
for (auto& c : controls)
{
c.second->setX(this->x + tabBarHeight);
c.second->setY(this->y);
c.second->setWidth(this->width - tabBarHeight);
c.second->setHeight(this->height);
}
nX = this->x + tabBarHeight;
nY = this->y;
for (auto& c : controls)
{
for (auto& v : c.second->getControls())
{
v->setX(v->getLocalX() + nX);
v->setY(v->getLocalY() + nY);
}
}
break;
case StellarX::TabPlacement::Right:
for (auto& c : controls)
{
c.second->setX(this->x);
c.second->setY(this->y);
c.second->setWidth(this->width - tabBarHeight);
c.second->setHeight(this->height);
}
nX = this->x;
nY = this->y;
for (auto& c : controls)
{
for (auto& v : c.second->getControls())
{
v->setX(v->getLocalX() + nX);
v->setY(v->getLocalY() + nY);
}
}
break;
default:
break;
}
}
TabControl::TabControl():Canvas()
{
this->id = "TabControl";
}
TabControl::TabControl(int x, int y, int width, int height)
: Canvas(x, y, width, height)
{
this->id = "TabControl";
}
TabControl::~TabControl()
{
}
void TabControl::draw()
{
if (!dirty || !show)return;
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
saveBackground(this->x, this->y, this->width, this->height);
// 恢复背景(清除旧内容)
restBackground();
Canvas::draw();
for (auto& c : controls)
{
c.first->setDirty(true);
c.first->draw();
}
for (auto& c : controls)
if(c.second->IsVisible())
{
c.second->setDirty(true);
c.second->draw();
}
dirty = false;
}
bool TabControl::handleEvent(const ExMessage& msg)
{
if (!show)return false;
bool consume = false;
for (auto& c : controls)
if (c.first->handleEvent(msg))
{
consume = true;
break;
}
for (auto& c : controls)
if(c.second->IsVisible())
if (c.second->handleEvent(msg))
{
consume = true;
break;
}
if (dirty)
requestRepaint();
return consume;
}
void TabControl::add(std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>&& control)
{
controls.push_back(std::move(control));
initTabBar();
initTabPage();
size_t idx = controls.size() - 1;
controls[idx].first->setParent(this);
controls[idx].first->enableTooltip(true);
controls[idx].first->setbuttonMode(StellarX::ButtonMode::TOGGLE);
controls[idx].first->setOnToggleOnListener([this,idx]()
{
controls[idx].second->setIsVisible(true);
controls[idx].second->onWindowResize();
for (auto& tab : controls)
{
if (tab.first->getButtonText() != controls[idx].first->getButtonText())
{
tab.first->setButtonClick(false);
tab.second->setIsVisible(false);
}
}
dirty = true;
});
controls[idx].first->setOnToggleOffListener([this,idx]()
{
controls[idx].second->setIsVisible(false);
dirty = true;
});
controls[idx].second->setParent(this);
controls[idx].second->setLinewidth(canvaslinewidth);
controls[idx].second->setIsVisible(false);
}
void TabControl::add(std::string tabText, std::unique_ptr<Control> control)
{
control->setDirty(true);
for (auto& tab : controls)
{
if (tab.first->getButtonText() == tabText)
{
control->setParent(tab.second.get());
control->setIsVisible( tab.second->IsVisible());
tab.second->addControl(std::move(control));
break;
}
}
}
void TabControl::setTabPlacement(StellarX::TabPlacement placement)
{
this->tabPlacement = placement;
setDirty(true);
initTabBar();
initTabPage();
}
void TabControl::setTabBarHeight(int height)
{
tabBarHeight = height;
setDirty(true);
initTabBar();
initTabPage();
}
void TabControl::setIsVisible(bool visible)
{
// 先让基类 Canvas 处理自己的回贴/丢快照逻辑
Canvas::setIsVisible(visible); // <--- 新增
this->show = visible;
for (auto& tab : controls)
{
tab.first->setIsVisible(visible);
//页也要跟着关/开,否则它们会保留旧的 saveBkImage
tab.second->setIsVisible(visible);
tab.second->setDirty(true);
}
}
void TabControl::onWindowResize()
{
Control::onWindowResize();
for (auto& c : controls)
{
c.first->onWindowResize();
c.second->onWindowResize();
}
}
int TabControl::getActiveIndex() const
{
int idx = -1;
for (auto& c : controls)
{
idx++;
if (c.first->isClicked())
return idx;
}
return idx;
}
void TabControl::setActiveIndex(int idx)
{
if (idx < 0 || idx > controls.size() - 1) return;
if (controls[idx].first->getButtonMode() == StellarX::ButtonMode::DISABLED)return;
if (controls[idx].first->isClicked())
{
if (controls[idx].second->IsVisible())
return;
else
controls[idx].second->setIsVisible(true);
}
else
{
controls[idx].first->setButtonClick(true);
}
}
int TabControl::count() const
{
return (int)controls.size();
}
int TabControl::indexOf(const std::string& tabText) const
{
int idx = -1;
for(auto& c : controls)
{
idx++;
if (c.first->getButtonText() == tabText)
return idx;
}
return idx;
}

View File

@@ -3,16 +3,18 @@
Label::Label()
:Control(0, 0, 0, 0)
{
this->id = "Label";
this->text = "默认标签";
textColor = RGB(0,0,0);
textStyle.color = RGB(0,0,0);
textBkColor = RGB(255, 255, 255);; //默认白色背景
}
Label::Label(int x, int y, std::string text, COLORREF textcolor, COLORREF bkColor)
:Control(x, y, 0, 0)
{
this->id = "Label";
this->text = text;
textColor = textcolor;
textStyle.color = textcolor;
textBkColor = bkColor; //默认白色背景
}
@@ -28,14 +30,21 @@ void Label::draw()
setbkmode(OPAQUE); //设置背景不透明
setbkcolor(textBkColor); //设置背景颜色
}
settextcolor(textColor);
settextcolor(textStyle.color);
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); //设置字体样式
this->saveBackground(x, y,textwidth(text.c_str()),textheight(text.c_str()));
this-> restBackground();
if (0 == this->width || 0 == this->height)
{
this->width = textwidth(text.c_str());
this->height = textheight(text.c_str());
}
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
saveBackground(this->x, this->y,this->width,this->height);
// 恢复背景(清除旧内容)
restBackground();
outtextxy(x, y, LPCTSTR(text.c_str()));
this->restoreStyle();
restoreStyle();
dirty = false;
}
}
@@ -52,12 +61,6 @@ void Label::setTextdisap(bool key)
this->dirty = true;
}
void Label::setTextColor(COLORREF color)
{
textColor = color;
this->dirty = true;
}
void Label::setTextBkColor(COLORREF color)
{
textBkColor = color;
@@ -68,4 +71,5 @@ void Label::setText(std::string text)
{
this->text = text;
this->dirty = true;
}

View File

@@ -7,8 +7,8 @@ void Table::drawTable()
// 表体从“表头之下”开始
dX = x + border;
dY = y + border + lineHeights.at(0) + 10; // 表头高度
uY = dY + lineHeights.at(0) + 10;
dY = y + border + lineHeights.at(0) + TABLE_HEADER_EXTRA; // 表头高度
uY = dY + lineHeights.at(0) + TABLE_ROW_EXTRA;
size_t startRow = (currentPage - 1) * rowsPerPage;
size_t endRow = startRow + (size_t)rowsPerPage < data.size() ? startRow + (size_t)rowsPerPage : data.size();
@@ -17,14 +17,14 @@ void Table::drawTable()
{
for (size_t j = 0; j < data[i].size(); ++j)
{
uX = dX + colWidths.at(j) + 20; // 列宽 + 20
uX = dX + colWidths.at(j) + TABLE_COL_GAP;
fillrectangle(dX, dY, uX, uY);
outtextxy(dX + 10, dY + 5, LPCTSTR(data[i][j].c_str()));
dX += colWidths.at(j) + 20;
outtextxy(dX + TABLE_PAD_X, dY + TABLE_PAD_Y, LPCTSTR(data[i][j].c_str()));
dX += colWidths.at(j) + TABLE_COL_GAP;
}
dX = x + border;
dY = uY;
uY = dY + lineHeights.at(0) + 10;
uY = dY + lineHeights.at(0) + TABLE_ROW_EXTRA;
}
}
@@ -36,14 +36,14 @@ void Table::drawHeader()
// 内容区原点 = x+border, y+border
dX = x + border;
dY = y + border;
uY = dY + lineHeights.at(0) + 10;
uY = dY + lineHeights.at(0) + TABLE_HEADER_EXTRA;
for (size_t i = 0; i < headers.size(); i++)
{
uX = dX + colWidths.at(i) + 20; // 注意这里是 +20和表体一致
uX = dX + colWidths.at(i) + TABLE_COL_GAP; // 注意这里是 +20和表体一致
fillrectangle(dX, dY, uX, uY);
outtextxy(dX + 10, dY + 5, LPCTSTR(headers[i].c_str()));
dX += colWidths.at(i) + 20; // 列间距 20
outtextxy(dX + TABLE_PAD_X, dY + TABLE_PAD_Y, LPCTSTR(headers[i].c_str()));
dX += colWidths.at(i) + TABLE_COL_GAP; // 列间距 20
}
}
// 遍历所有数据单元和表头,计算每列的最大宽度和每行的最大高度,
@@ -51,9 +51,9 @@ void Table::drawHeader()
void Table::initTextWaH()
{
// 和绘制一致的单元内边距
const int padX = 10; // 左右 padding
const int padY = 5; // 上下 padding
const int colGap = 20; // 列间距
const int padX = TABLE_PAD_X; // 左右 padding
const int padY = TABLE_PAD_Y; // 上下 padding
const int colGap = TABLE_COL_GAP; // 列间距
const int border = tableBorderWidth > 0 ? tableBorderWidth : 0;
// 统计每列最大文本宽 & 每列最大行高(包含数据 + 表头)
@@ -101,9 +101,9 @@ void Table::initTextWaH()
// 页脚:
const int pageTextH = textheight(LPCTSTR(pageNumtext.c_str()));
const int btnTextH = textheight(LPCTSTR("上一页"));
const int btnPadV = 8;
const int btnPadV = TABLE_BTN_TEXT_PAD_V;
const int btnH = btnTextH + 2 * btnPadV;
const int footerPad = 16;
const int footerPad = TABLE_FOOTER_PAD;
const int footerH = (pageTextH > btnH ? pageTextH : btnH) + footerPad;
// 最终表宽/高:内容 + 对称边框
@@ -113,16 +113,16 @@ void Table::initTextWaH()
void Table::initButton()
{
const int gap = 12; // 页码与按钮之间的固定间距
const int padH = 12; // 按钮水平内边距
const int padV = 0; // 按钮垂直内边距
const int gap = TABLE_BTN_GAP;
const int padH = TABLE_BTN_PAD_H;
const int padV = TABLE_BTN_PAD_V; // 按钮垂直内边距
int pageW = textwidth(LPCTSTR(pageNumtext.c_str()));
int lblH = textheight(LPCTSTR(pageNumtext.c_str()));
// 统一按钮尺寸(用按钮文字自身宽高 + padding
int prevW = textwidth(LPCTSTR("上一页")) + padH * 2;
int nextW = textwidth(LPCTSTR("下一页")) + padH * 2;
int prevW = textwidth(LPCTSTR(TABLE_STR_PREV)) + padH * 2;
int nextW = textwidth(LPCTSTR(TABLE_STR_NEXT)) + padH * 2;
int btnH = lblH + padV * 2;
@@ -133,7 +133,7 @@ void Table::initButton()
int btnY = pY; // 和页码同一基线
if (!prevButton)
prevButton = new Button(prevX, btnY, prevW, btnH, "上一页", RGB(0, 0, 0), RGB(255, 255, 255));
prevButton = new Button(prevX, btnY, prevW, btnH, TABLE_STR_PREV, RGB(0, 0, 0), RGB(255, 255, 255));
else
{
prevButton->setX(prevX);
@@ -141,7 +141,7 @@ void Table::initButton()
}
if (!nextButton)
nextButton = new Button(nextX, btnY, nextW, btnH, "下一页", RGB(0, 0, 0), RGB(255, 255, 255));
nextButton = new Button(nextX, btnY, nextW, btnH, TABLE_STR_NEXT, RGB(0, 0, 0), RGB(255, 255, 255));
else
{
nextButton->setX(nextX);
@@ -178,20 +178,20 @@ void Table::initPageNum()
// 统一坐标系
const int border = tableBorderWidth > 0 ? tableBorderWidth : 0;
const int baseH = lineHeights.empty() ? 0 : lineHeights.at(0);
const int headerH = baseH + 10;
const int rowsH = baseH * rowsPerPage + rowsPerPage * 10;
const int headerH = baseH + TABLE_HEADER_EXTRA;
const int rowsH = baseH * rowsPerPage + rowsPerPage * TABLE_ROW_EXTRA;
// 内容宽度 = sum(colWidths + 20)initTextWaH() 已把 this->width += 2*border
// 因此 contentW = this->width - 2*border 更稳妥
const int contentW = this->width - (border << 1);
// 页脚顶部位置(表头 + 可视数据区 之后)
pY = y + border + headerH + rowsH + 8; // +8 顶部留白
pY = y + border + headerH + rowsH + TABLE_FOOTER_BLANK; // +8 顶部留白
// 按理来说 x + (this->width - textW) / 2;就可以
// 但是在绘制时发现控件偏右因此减去40
int textW = textwidth(LPCTSTR(pageNumtext.c_str()));
pX = x - 40 +(this->width - textW) / 2;
pX = x + TABLE_PAGE_TEXT_OFFSET_X +(this->width - textW) / 2;
if (!pageNum)
pageNum = new Label(pX, pY, pageNumtext);
@@ -217,6 +217,7 @@ void Table::drawPageNum()
if (nullptr == pageNum)
initPageNum();
pageNum->setText(pageNumtext);
pageNum->textStyle = this->textStyle;
if (StellarX::FillMode::Null == tableFillMode)
pageNum->setTextdisap(true);
pageNum->draw();
@@ -244,6 +245,7 @@ void Table::drawButton()
Table::Table(int x, int y)
:Control(x, y, 0, 0)
{
this->id = "Table";
}
Table::~Table()
@@ -264,6 +266,28 @@ Table::~Table()
void Table::draw()
{
//在这里先初始化保证翻页按钮不为空
// 在一些容器中Table不会被立即绘制可能导致事件事件传递时触发空指针警报
// 由于单元格初始化依赖字体数据所以先设置一次字体样式
// 先保存当前绘图状态
saveStyle();
// 设置表格样式
setfillcolor(tableBkClor);
setlinecolor(tableBorderClor);
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
settextcolor(textStyle.color);
setlinestyle((int)tableLineStyle, tableBorderWidth);
setfillstyle((int)tableFillMode);
// 是否需要计算单元格尺寸
if (isNeedCellSize)
{
initTextWaH();
isNeedCellSize = false;
}
restoreStyle();
if (this->dirty && this->show)
{
// 先保存当前绘图状态
@@ -280,13 +304,6 @@ void Table::draw()
setfillstyle((int)tableFillMode);
setbkmode(TRANSPARENT);
// 是否需要计算单元格尺寸
if (isNeedCellSize)
{
initTextWaH();
isNeedCellSize = false;
}
if (isNeedDrawHeaders)
{
// 重新设置表格样式
@@ -302,7 +319,7 @@ void Table::draw()
}
//确保在绘制任何表格内容之前捕获背景
// 临时恢复样式,确保捕获正确的背景
if (!saveBkImage)
if ((!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height)||!saveBkImage)
saveBackground(this->x, this->y, this->width, this->height);
// 恢复背景(清除旧内容)
restBackground();
@@ -312,11 +329,8 @@ void Table::draw()
dY = y;
drawHeader();
this->isNeedDrawHeaders = false;
// 绘制当前页
drawTable();
// 绘制页码标签
drawPageNum();
@@ -324,6 +338,7 @@ void Table::draw()
if (this->isShowPageButton)
drawButton();
// 恢复绘图状态
restoreStyle();
dirty = false; // 标记不需要重绘
@@ -338,12 +353,12 @@ bool Table::handleEvent(const ExMessage& msg)
return consume;
else
{
consume = prevButton->handleEvent(msg);
if (!consume)
if(prevButton)consume = prevButton->handleEvent(msg);
if (nextButton&&!consume)
consume = nextButton->handleEvent(msg);
}
if (dirty)
draw();
requestRepaint();
return consume;
}

View File

@@ -5,6 +5,7 @@
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)
{
this->id = "TextBox";
}
void TextBox::draw()
@@ -27,7 +28,10 @@ void TextBox::draw()
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)
saveBackground(this->x, this->y, this->width, this->height);
// 恢复背景(清除旧内容)
restBackground();
//根据形状绘制
switch (shape)
{
@@ -87,7 +91,7 @@ bool TextBox::handleEvent(const ExMessage& msg)
flushmessage(EX_MOUSE | EX_KEY);
}
if (dirty)
draw();
requestRepaint();
if (click)
click = false;

View File

@@ -38,8 +38,8 @@ Window::~Window()
void Window::draw() {
// 使用 EasyX 创建基本窗口
if (!hWnd)
hWnd = initgraph(width, height, windowMode);
SetWindowText(hWnd, headline.c_str());
// **启用窗口拉伸支持**:添加厚边框和最大化按钮样式
LONG style = GetWindowLong(hWnd, GWL_STYLE);
style |= WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX; // 可调整边框,启用最大化/最小化按钮
@@ -65,6 +65,7 @@ void Window::draw(std::string imagePath)
// 使用指定图片绘制窗口背景(铺满窗口)
this->background = new IMAGE(width, height);
bkImageFile = imagePath;
if (!hWnd)
hWnd = initgraph(width, height, windowMode);
SetWindowText(hWnd, headline.c_str());
loadimage(background, imagePath.c_str(), width, height, true);
@@ -200,8 +201,7 @@ int Window::runEventLoop()
// 标记所有控件/对话框为脏,确保都补一次背景/外观
for (auto& c : controls)
{
c->setDirty(true);
c->updateBackground();
c->onWindowResize();
c->draw();
}
for (auto& d : dialogs)
@@ -209,7 +209,7 @@ int Window::runEventLoop()
auto dd = dynamic_cast<Dialog*>(d.get());
dd->setDirty(true);
dd->setInitialization(true);
dd->draw();
d->draw();
}
needResizeDirty = false;
}