Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 58d4e8ab2f | |||
| 5420bfd644 | |||
| cc08187ced | |||
| c10e72b3fe | |||
| 6218ba54e3 | |||
| 7b087e296a | |||
| 8dee285de8 | |||
| d16b9bc226 | |||
| 270c6f5293 | |||
| 4bb0352088 | |||
|
|
c4852d080f | ||
|
|
dcf13895da | ||
|
|
95149238e2 | ||
|
|
3509b2bc39 | ||
|
|
26f30cee39 | ||
|
|
741987e48b | ||
|
|
9226deaf85 | ||
|
|
9f2b175b17 |
29
.github/workflows/mirror-to-gitcode.yml
vendored
Normal file
29
.github/workflows/mirror-to-gitcode.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
name: Mirror to GitCode
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "**" ] # 任意分支推送触发
|
||||||
|
create:
|
||||||
|
tags: [ "*" ] # 新建 tag 触发
|
||||||
|
workflow_dispatch: # 支持手动触发
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mirror:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # 必须拿全历史+所有 tag
|
||||||
|
- name: Push --mirror to GitCode
|
||||||
|
env:
|
||||||
|
GITCODE_USER: ${{ secrets.GITCODE_USER }}
|
||||||
|
GITCODE_TOKEN: ${{ secrets.GITCODE_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
git config --global user.name "mirror-bot"
|
||||||
|
git config --global user.email "mirror-bot@users.noreply.github.com"
|
||||||
|
# 如果你的命名空间不是个人用户,而是组织,请把 ${GITCODE_USER} 换成组织名
|
||||||
|
git remote add gitcode https://${GITCODE_USER}:${GITCODE_TOKEN}@gitcode.com/${GITCODE_USER}/StellarX.git
|
||||||
|
git push --prune --mirror gitcode
|
||||||
84
.gitignore
vendored
84
.gitignore
vendored
@@ -1,25 +1,13 @@
|
|||||||
# 编译生成文件
|
# 编译输出目录
|
||||||
*.exe
|
/build/
|
||||||
*.ilk
|
/bin/
|
||||||
*.obj
|
/lib/
|
||||||
*.pdb
|
|
||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.lib
|
|
||||||
*.dll
|
|
||||||
*.exp
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
|
|
||||||
# 构建目录
|
# CMake 生成文件
|
||||||
build/
|
/CMakeFiles/
|
||||||
bin/
|
/CMakeCache.txt
|
||||||
obj/
|
/cmake_install.cmake
|
||||||
Debug/
|
/Makefile
|
||||||
Release/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
Win32/
|
|
||||||
|
|
||||||
# Visual Studio 文件
|
# Visual Studio 文件
|
||||||
.vs/
|
.vs/
|
||||||
@@ -27,51 +15,15 @@ Win32/
|
|||||||
*.vcxproj
|
*.vcxproj
|
||||||
*.vcxproj.filters
|
*.vcxproj.filters
|
||||||
*.vcxproj.user
|
*.vcxproj.user
|
||||||
*.suo
|
|
||||||
*.ncb
|
|
||||||
*.user
|
|
||||||
*.sdf
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.res
|
|
||||||
*.tlog
|
|
||||||
*.lastbuildstate
|
|
||||||
*.idb
|
|
||||||
*.pch
|
|
||||||
|
|
||||||
# CMake 生成文件
|
# 编译产物
|
||||||
CMakeFiles/
|
*.obj
|
||||||
CMakeCache.txt
|
*.pdb
|
||||||
cmake_install.cmake
|
*.log
|
||||||
Makefile
|
*.exe
|
||||||
*.cmake
|
*.dll
|
||||||
*.cmake.*
|
*.lib
|
||||||
CTestTestfile.cmake
|
|
||||||
Testing/
|
|
||||||
_deps/
|
|
||||||
|
|
||||||
# 文档生成
|
|
||||||
docs/html/
|
|
||||||
docs/latex/
|
|
||||||
docs/man/
|
|
||||||
docs/rtf/
|
|
||||||
docs/xml/
|
|
||||||
|
|
||||||
# 临时文件
|
# 临时文件
|
||||||
*.tmp
|
*.cache
|
||||||
*.temp
|
*.zip
|
||||||
*.log
|
|
||||||
*.swp
|
|
||||||
*.swo
|
|
||||||
|
|
||||||
# EasyX 临时文件
|
|
||||||
*.jpg
|
|
||||||
*.bmp
|
|
||||||
*.png
|
|
||||||
*.tga
|
|
||||||
|
|
||||||
# 其他
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
ehthumbs.db
|
|
||||||
Desktop.ini
|
|
||||||
|
|||||||
293
CHANGELOG.en.md
Normal file
293
CHANGELOG.en.md
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to the StellarX project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
[中文文档](CHANGELOG.md)
|
||||||
|
|
||||||
|
## [v2.3.0] - 2025-11-18
|
||||||
|
|
||||||
|
### ✨ Added
|
||||||
|
|
||||||
|
- Introduced `LayoutMode` adaptive layout mode and `Anchor` anchor points. Controls can now call `setLayoutMode` to set layout mode and `steAnchor` to set anchor points, enabling controls to adapt to window changes during resizing. Dialog controls only recalculate their position during window resizing to maintain center alignment.
|
||||||
|
- Added `adaptiveLayout()` API to `Window`, called by the main event loop to recalculate control positions and sizes based on anchor points during window resizing, enabling dual-anchored controls (left-right or top-bottom) to automatically stretch with window changes.
|
||||||
|
|
||||||
|
### ⚙️ Changed
|
||||||
|
|
||||||
|
- **Optimized Window Resizing Mechanism**: Refactored `WndProcThunk`, `runEventLoop`, and `pumpResizeIfNeeded` to uniformly record window size changes and perform one-time repainting at the end of the event loop, preventing jitter and sequencing issues caused by repeated drawing during resizing.
|
||||||
|
- **Added Dialog Size Scheduling Interface**: Introduced `Window::scheduleResizeFromModal()`, used in conjunction with `pumpResizeIfNeeded()`. During modal dialog display, the parent window can update client area size in real time and relayout child controls during unified finalization, while the dialog maintains its original size.
|
||||||
|
- **Redraw Sequence Optimization**: Replaced `InvalidateRect` with `ValidateRect` during the finalization phase after window size changes, preventing the system from sending additional `WM_PAINT` messages that could cause reentrant drawing.
|
||||||
|
- **Other Improvements**: Fixed delayed background snapshot updates for table pagination buttons and page number labels; improved dialog background capture logic.
|
||||||
|
|
||||||
|
### ✅ Fixed
|
||||||
|
|
||||||
|
- **Modal Dialog Resizing Fix**: Resolved the issue where window resizing during modal dialog display prevented underlying controls from updating their sizes and positions according to anchor points; simultaneously eliminated ghosting artifacts caused by repeated dialog redrawing.
|
||||||
|
- **Drawing Sequence Disorder Fix**: Addressed sporadic drawing sequence disorders, control ghosting, and border flickering during window resizing, ensuring controls are drawn in the order they were added.
|
||||||
|
- **Stability Fixes**: Corrected abnormal frames caused by sudden window size changes in certain scenarios; resolved delayed background snapshot updates for tables and dialogs under edge conditions.
|
||||||
|
|
||||||
|
## [v2.2.2] - 2025 - 11- 08
|
||||||
|
|
||||||
|
### ⚙️ Changes
|
||||||
|
|
||||||
|
- Modified the coordinate transfer method for the Canvas container. Child control coordinates are now passed as relative coordinates (with the origin at the top-left corner of the container, obtainable via the `getX`/`getY` interface), instead of the original global coordinates. Child control coordinates can now be set to negative values.
|
||||||
|
- The example under `examples\register-viewer` has been updated to the latest version, aligning container child controls to use relative coordinates.
|
||||||
|
|
||||||
|
### ✅ Fixes
|
||||||
|
|
||||||
|
- Fixed jittering/bouncing and flickering when resizing the window (left/top edges):
|
||||||
|
- `WM_SIZING` only clamps the minimum size; `WM_GETMINMAXINFO` sets the window-level minimum tracking size.
|
||||||
|
- Freezes redrawing during dragging and handles resizing uniformly upon release; `WM_SIZE` only records the new size without interfering with drawing.
|
||||||
|
- Disabled `WM_ERASEBKGND` background erasing and removed `CS_HREDRAW`/`CS_VREDRAW` to reduce flickering.
|
||||||
|
- Fixed issues related to dialog boxes:
|
||||||
|
- Resolved occasional residual functional buttons after closing a dialog.
|
||||||
|
- Fixed issues where window resizing failed to redraw or displayed a corrupted background when a modal dialog was active.
|
||||||
|
- Fixed delayed updates of background snapshots for the table control's pagination buttons and page number labels during window changes.
|
||||||
|
|
||||||
|
## [v2.2.1] - 2025-11-04
|
||||||
|
|
||||||
|
==This release is a hotfix for v2.2.0==
|
||||||
|
|
||||||
|
### ✅ Fixed
|
||||||
|
|
||||||
|
- The `TabControl` class overrode the base class's `setDirty` method to ensure synchronized update status between the tab and its page list.
|
||||||
|
- The `Canvas` container, special container `TabControl`, and dialog `Dialog` overrode the `requestRepaint` method. When control bubbling propagates upward, the parent pointer is passed. Repaint requests now only bubble up one level to the parent and no longer propagate to the root. Furthermore, the entire parent container is no longer repainted; instead, the parent container repaints only the dirtied child controls, avoiding flickering caused by frequent repaints of the entire container.
|
||||||
|
- The `saveBackground` and `restoreBackground` methods were overridden in `Dialog` to ensure no border remnants remain after the dialog is closed.
|
||||||
|
|
||||||
|
### ⚙️ Changed
|
||||||
|
|
||||||
|
- Completely disabled copy and move semantics for the `Control` class:
|
||||||
|
`Control(const Control&) = delete;`
|
||||||
|
`Control& operator=(const Control&) = delete;`
|
||||||
|
`Control(Control&&) = delete;`
|
||||||
|
`Control& operator=(Control&&) = delete;`
|
||||||
|
|
||||||
|
## [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 page’s 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 control’s 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 Label’s 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 didn’t 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 control’s 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 control’s 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 there’s 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 framework’s 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.
|
||||||
|
|
||||||
|
### ✨ Added
|
||||||
|
|
||||||
|
- **Bilingual API Documentation (Chinese and English)**
|
||||||
|
- The documentation provides a detailed introduction of each class, including API descriptions, functionalities, and points to note, with a comprehensive explanation of each control.
|
||||||
|
|
||||||
|
- **Window resize/maximize reinforcement (EasyX + Win32)**
|
||||||
|
- `Window::enableResize(bool enable, int minW, int minH)`; toggle at runtime and set min track size.
|
||||||
|
- Subclassed WndProc for `WM_GETMINMAXINFO / WM_SIZE / WM_EXITSIZEMOVE / WM_ERASEBKGND / WM_PAINT` with `WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CLIPCHILDREN | WS_CLIPSIBLINGS`.
|
||||||
|
- **Full-surface background paint** in `WM_PAINT` (solid/image), removing black borders and maximize ghosts.
|
||||||
|
- **One-shot redraw** on resize with `pendingW/H + needResizeDirty` coalescing.
|
||||||
|
- **Layout manager (phase 1)**
|
||||||
|
- On `Canvas`: `LayoutKind::{Absolute, HBox, VBox, Grid(placeholder), Flow(placeholder), Stack(placeholder)}` with `LayoutParams` (`margins`, `fixedW/fixedH(-1=same)`, `weight`, `Align{Start,Center,End,Stretch}`).
|
||||||
|
- Implemented **HBox/VBox** auto layout inside containers; containers stay absolutely positioned; nesting supported.
|
||||||
|
- **Tabs control (early)**
|
||||||
|
- Decoupled tab strip and page container; background snapshot for transparent themes.
|
||||||
|
- **Button single-line truncation (MBCS; CN/EN aware)**
|
||||||
|
- Pixel-width threshold with `...` avoiding half-glyph artifacts.
|
||||||
|
- **Hover tooltip**
|
||||||
|
- Implemented via `Label` with delay & auto-hide; per-control background snapshot/restore; customizable text with sensible fallback.
|
||||||
|
|
||||||
|
### 🔧 Changed
|
||||||
|
|
||||||
|
- **Window rendering path**
|
||||||
|
- Reduced reliance on “offscreen frame blit”; in `WM_PAINT` use **GDI full background + EasyX batch drawing** (`BeginBatchDraw/EndBatchDraw + FlushBatchDraw`) to suppress flicker.
|
||||||
|
- On resize, only rebuild the scaled background (`zoomBackground`); actual painting happens next frame.
|
||||||
|
- **Control base**
|
||||||
|
- Standardized **captureBackground/restoreBackground**; transparent/stacked visuals are stable.
|
||||||
|
- Unified `dirty`: containers paint their own background when dirty; **children still evaluate/draw** as needed each frame.
|
||||||
|
- **Table**
|
||||||
|
- In transparent mode, pagination widgets now inherit table text/fill style.
|
||||||
|
- Reworked **pagination math + block centering** (header included in available width).
|
||||||
|
- Fixed background snapshot sizing (header inclusion) that caused failed restores.
|
||||||
|
- **Event loop**
|
||||||
|
- Coalesced `WM_SIZE` to the loop tail to avoid redraw storms and pointer-hover lag.
|
||||||
|
|
||||||
|
### ✅ Fixed
|
||||||
|
|
||||||
|
- **Black borders / maximize ghost / flicker**: blocked system background erase; full-surface paint in `WM_PAINT`; cleared clipping to prevent stale fragments.
|
||||||
|
- **Containers not drawn; controls only after interaction**: first-frame & post-input full-tree dirty; children draw even if the container isn’t dirty.
|
||||||
|
- **Table pagination overlap & transparent shadowing**: corrected snapshot area; recomputed coordinates; instant restore + redraw after paging.
|
||||||
|
|
||||||
|
### ⚠️ Breaking
|
||||||
|
|
||||||
|
- If external code accessed `Window` private members (e.g., `dialogs`), use `getControls()` / `getdialogs()`.
|
||||||
|
- Pagination math & header inclusion may shift hard-coded offsets in custom renderers.
|
||||||
|
- Custom controls that don’t restore `SetWorkingImage(nullptr)` before drawing should be reviewed.
|
||||||
|
|
||||||
|
### 📌 Upgrade notes
|
||||||
|
|
||||||
|
1. Migrate manual `cleardevice()+putimage` paths to unified **full-surface background** in `WM_PAINT`.
|
||||||
|
2. For transparent controls, `captureBackground()` before first draw; `restoreBackground()` when hiding/overdrawing.
|
||||||
|
3. For layout, set container `layout.kind = HBox/VBox` and child `LayoutParams` (`margin/fixed/weight/align`).
|
||||||
|
4. Keep a **single** truncation pass for buttons to avoid duplicate `...`.
|
||||||
|
5. Prefer built-in table pagination.
|
||||||
|
|
||||||
|
## [v2.0.1] - 2025-10-03
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- New example: 32-bit register viewer tool implemented based on StellarX (supports bit inversion, left shift, right shift, hexadecimal/decimal signed/unsigned toggle, grouped binary display).
|
||||||
|
- Example path: `examples/register-viewer/`
|
||||||
|
- `TextBox` added `setText` API, allowing external setting of text box content
|
||||||
|
- `TextBox::setText` API modified: immediately calls `draw` method to redraw after setting text
|
||||||
|
- `Button` added `setButtonClick` API, allowing external functions to modify button click state and execute corresponding callback functions
|
||||||
|
- ==All documents updated with corresponding English versions(.en)==
|
||||||
|
|
||||||
|
## [v2.0.0] - 2025-09-21
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
v2.0.0 is a major release. This release introduces dialog and message box factory (Dialog / MessageBox), with several important fixes and improvements to event distribution, API semantics, and internal resource management.
|
||||||
|
|
||||||
|
Some APIs/behaviors have breaking changes that are not backward compatible.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Dialog System**:
|
||||||
|
- Added `Dialog` class, inheriting from `Canvas`, for building modal and non-modal dialogs
|
||||||
|
- Added `MessageBox` factory class, providing two convenient APIs: `ShowModal` (synchronous blocking) and `ShowAsync` (asynchronous callback)
|
||||||
|
- Supports six standard message box types: `OK`, `OKCancel`, `YesNo`, `YesNoCancel`, `RetryCancel`, `AbortRetryIgnore`
|
||||||
|
- Automatically handles dialog layout, background saving and restoration, event propagation, and lifecycle management
|
||||||
|
|
||||||
|
- **Enhanced Event System**:
|
||||||
|
- All controls' `handleEvent` methods now return `bool` type, indicating whether the event was consumed
|
||||||
|
- Introduced event consumption mechanism, supporting finer-grained event propagation control
|
||||||
|
- Window class event loop now prioritizes dialog event processing
|
||||||
|
|
||||||
|
- **Control State Management**:
|
||||||
|
- Control base class added `dirty` flag and `setDirty()` method, uniformly managing redraw state
|
||||||
|
- All controls now implement `IsVisible()` and `model()` methods
|
||||||
|
|
||||||
|
- **API Enhancements**:
|
||||||
|
- Button class added `setButtonFalseColor()` method
|
||||||
|
- TextBox class `setMaxCharLen()` now accepts `size_t` type parameter
|
||||||
|
- Window class added dialog management methods and duplicate detection mechanism
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
- **API Signature Changes**:
|
||||||
|
- All controls' `handleEvent(const ExMessage& msg)` method changed from returning `void` to returning `bool`
|
||||||
|
- Control base class added pure virtual functions `IsVisible() const` and `model() const`, all derived classes must implement them
|
||||||
|
|
||||||
|
- **Resource Management Changes**:
|
||||||
|
- Control base class style saving changed from stack objects to heap objects, managed using pointers
|
||||||
|
- Enhanced resource release safety, all resources are properly released in destructors
|
||||||
|
|
||||||
|
- **Event Handling Logic**:
|
||||||
|
- Window's `runEventLoop()` method completely rewritten, now prioritizes dialog events
|
||||||
|
- Introduced event consumption mechanism, events do not continue propagating after being consumed
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Memory Management**:
|
||||||
|
- Fixed memory leak issue in `Button::setFillIma()`
|
||||||
|
- Fixed resource release issues in Control base class destructor
|
||||||
|
- Fixed background image resource management issues in Dialog class
|
||||||
|
|
||||||
|
- **Layout and Rendering**:
|
||||||
|
- Fixed pagination calculation, column width, and row height boundary issues in `Table` component
|
||||||
|
- Fixed layout disorder caused by `pX` coordinate accumulation error in `Table`
|
||||||
|
- Fixed dirty determination issue in `Canvas::draw()` that prevented child controls from being drawn
|
||||||
|
- Fixed asymmetric call issues between `TextBox::draw()` and `restoreStyle()`
|
||||||
|
|
||||||
|
- **Event Handling**:
|
||||||
|
- Fixed window event distribution logic to ensure dialog/top-level controls prioritize event processing
|
||||||
|
- Fixed delayed state updates when mouse moves out of button area
|
||||||
|
- Fixed race conditions in non-modal dialog event handling
|
||||||
|
|
||||||
|
- **Other Issues**:
|
||||||
|
- Fixed potential errors in text measurement and rendering
|
||||||
|
- Fixed incomplete background restoration after dialog closure
|
||||||
|
- Fixed z-order management issues in multi-dialog scenarios
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **Code Quality**:
|
||||||
|
- Refactored numerous internal APIs, enhancing exception safety and maintainability
|
||||||
|
- Used smart pointers and modern C++ features to replace raw new/delete
|
||||||
|
- Unified code style and naming conventions
|
||||||
|
|
||||||
|
- **Performance Optimization**:
|
||||||
|
- Optimized event processing flow, reducing unnecessary redraws
|
||||||
|
- Improved dialog background saving and restoration mechanism
|
||||||
|
- Reduced memory allocation and copy operations
|
||||||
|
|
||||||
|
- **Documentation and Examples**:
|
||||||
|
- Added detailed usage examples for all new features
|
||||||
|
- Improved code comments and API documentation
|
||||||
|
- Updated README and CHANGELOG to reflect latest changes
|
||||||
|
|
||||||
|
## [1.1.0] - 2025-09-08
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Window Class API Enhancements**:
|
||||||
|
- Added complete getter method set, improving class encapsulation and usability
|
||||||
|
- `getHwnd()` - Get window handle for integration with native Windows API
|
||||||
|
- `getWidth()` - Get window width
|
||||||
|
- `getHeight()` - Get window height
|
||||||
|
- `getHeadline()` - Get window title
|
||||||
|
- `getBkcolor()` - Get window background color
|
||||||
|
- `getBkImage()` - Get window background image pointer
|
||||||
|
- `getControls()` - Get reference to control management container, allowing iteration and manipulation of added controls
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
- **API Consistency**: Provided symmetric setter and getter methods for all important attributes
|
||||||
|
- **Code Documentation**: Further improved class comments, making them clearer and more professional
|
||||||
|
|
||||||
|
## [1.0.0] - 2025-09-08
|
||||||
|
|
||||||
|
### Release Summary
|
||||||
|
First stable release
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- First stable version of StellarX framework
|
||||||
|
- Complete control library: Button, Label, TextBox, Canvas, Table, and Window
|
||||||
|
- CMake-based build system
|
||||||
|
- Detailed documentation and example code
|
||||||
|
- **Explicit declaration: This framework is specifically designed for Windows platform**
|
||||||
|
|
||||||
|
### Released
|
||||||
|
- **First release of pre-compiled binary library files**, facilitating quick integration without compiling from source
|
||||||
|
- Provided release packages include:
|
||||||
|
- `StellarX-v1.0.0-x64-Windows-msvc-x64.zip`
|
||||||
|
- **Build Environment**: Visual Studio 2022 (MSVC v143)
|
||||||
|
- **Architecture**: x64 (64-bit)
|
||||||
|
- **Runtime Library**: `/MD`
|
||||||
|
- **Build Modes**: `Release | Debug`
|
||||||
|
- **Contents**: Includes all necessary header files (`include/`) and static library files (`lib/StellarX-Debug.lib StellarX-Release.lib`)
|
||||||
|
|
||||||
|
### Core Features
|
||||||
|
- Modular design following SOLID principles
|
||||||
|
- Unified control interface (`draw()` and `handleEvent()`)
|
||||||
|
- Support for multiple control shapes and styles
|
||||||
|
- Custom event handling callbacks
|
||||||
|
- Lightweight design with no external dependencies
|
||||||
|
|
||||||
|
## [0.1.0] - 2025-08-15
|
||||||
|
### Added
|
||||||
|
- Initial project structure and core architecture
|
||||||
|
- Control base class and basic event handling system
|
||||||
|
- Basic examples and documentation setup
|
||||||
255
CHANGELOG.md
255
CHANGELOG.md
@@ -5,6 +5,261 @@ 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)
|
||||||
|
|
||||||
|
## [v2.3.0] - 2025 - 11 - 18
|
||||||
|
|
||||||
|
### ✨ 新增
|
||||||
|
|
||||||
|
- 新增`LayoutMode `自适应布局模式和`Anchor`锚点,控件中可以调用 `setLayoutMode`
|
||||||
|
设置布局模式以及`steAnchor`设置锚点,达到窗口拉伸时控件自适应窗口变化。对话框控件在窗口变化时只会重新计算位置,来保证居中显示
|
||||||
|
- `Window`新增`adaptiveLayout()`这个API由主事件循环调用,在窗口拉伸时根据锚点重新计算控件位置和尺寸,使左右/上下双锚定控件随窗口变化自动伸缩。
|
||||||
|
|
||||||
|
### ⚙️ 变更
|
||||||
|
|
||||||
|
- **优化窗口尺寸调整机制**:重构 `WndProcThunk`、`runEventLoop` 和 `pumpResizeIfNeeded`,统一记录窗口尺寸变化并在事件循环尾部一次性重绘,避免拉伸过程中重复绘制引发抖动与顺序错乱。
|
||||||
|
- **新增对话框尺寸调度接口**:引入 `Window::scheduleResizeFromModal()`,配合 `pumpResizeIfNeeded()` 使用。模态对话框显示期间,父窗口可实时更新客户区尺寸并在统一收口时重新布局子控件,对话框自身尺寸保持不变。
|
||||||
|
- **重绘顺序优化**:在窗口尺寸变化后的收口阶段使用 `ValidateRect` 代替 `InvalidateRect`,避免系统再次发送 `WM_PAINT` 导致重入绘制。
|
||||||
|
- **其他改进**:修复表格翻页按钮与页码标签等元素背景快照更新不及时的问题;改进对话框背景捕捉逻辑。
|
||||||
|
|
||||||
|
### ✅ 修复
|
||||||
|
|
||||||
|
- **模态对话框拉伸修复**:解决了模态对话框弹出时,窗口拉伸无法让底层控件按照锚点更新尺寸和位置的问题;同时避免对话框反复重绘导致残影。
|
||||||
|
- **绘制顺序错乱修复**:解决窗口拉伸时偶发的绘制顺序紊乱、控件残影和边框闪烁问题,确保控件按添加顺序依次绘制。
|
||||||
|
- **稳定性修复**:修正某些情况下窗口尺寸突变导致的异常帧;解决表格和对话框背景快照在边界条件下未及时更新的问题。
|
||||||
|
|
||||||
|
## [v2.2.2] - 2025 - 11- 08
|
||||||
|
|
||||||
|
### ⚙️ 变更
|
||||||
|
|
||||||
|
- Canvas容器坐标传递方式改变,子控件坐标由原来的传递全局坐标改为传递相对坐标(坐标原点为容器的左上角坐标可通过getX/Y接口获得)可以设置子控件坐标为负值
|
||||||
|
- examples\register-viewer下的案例已同步修改为最新,同步容器子控件为相对坐标
|
||||||
|
|
||||||
|
### ✅ 修复
|
||||||
|
|
||||||
|
- 修复窗口拉伸(左/上边)时的抖动/弹回与闪烁
|
||||||
|
- `WM_SIZING` 仅做最小尺寸夹紧;`WM_GETMINMAXINFO` 设置窗口级最小轨迹
|
||||||
|
- 拖拽期冻结重绘,松手统一收口;`WM_SIZE` 只记录尺寸不抢绘制
|
||||||
|
- 禁用 `WM_ERASEBKGND` 擦背景并移除 `CS_HREDRAW/CS_VREDRAW`,减少闪烁
|
||||||
|
- 对话框的相关问题
|
||||||
|
- 对话框关闭后概率出现功能按钮残留
|
||||||
|
- 模态对话框触发时,窗口拉伸无法重绘或背景错乱
|
||||||
|
- 表格控件在窗口变化时其翻页按钮和页码标签背景快照更新不及时的问题
|
||||||
|
|
||||||
|
## [v2.2.1] - 2025 - 11 - 4
|
||||||
|
|
||||||
|
==此版本为v2.2.0的修复版本==
|
||||||
|
|
||||||
|
### ✅ 修复
|
||||||
|
|
||||||
|
- `TabControl`类重写了基类的`setDirty`方法保证页签+页列表同步更新
|
||||||
|
|
||||||
|
状态
|
||||||
|
|
||||||
|
- `Canvas`容器和特殊容器`TabControl`以及对话框`Dialog`重写`requestRepaint`方法,控件向上冒泡时传递父指针,请求重绘时只向上到父一级,不再传递到根。并且不再重绘整个父容器,而是由父容器重绘标脏的子控件,避免了频繁真个容器重绘导致的频闪
|
||||||
|
|
||||||
|
- `Dialog`中重写了`saveBackground`和`restBackground`方法,保证对话框关闭后不会有边框残留
|
||||||
|
|
||||||
|
### ⚙️ 变更
|
||||||
|
|
||||||
|
- 彻底禁用`Control`的移动构造`Control(const Control&) = delete;`
|
||||||
|
`Control& operator=(const Control&) = delete;`
|
||||||
|
`Control(Control&&) = delete;`
|
||||||
|
`Control& operator=(Control&&) = delete;`
|
||||||
|
|
||||||
|
## [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` 承载,点击不同页签(Button,TOGGLE模式)将切换显示对应页面**【见 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** 的机制。
|
||||||
|
|
||||||
|
### ✨ 新增
|
||||||
|
|
||||||
|
- **中英文双语 API文档**
|
||||||
|
- 文档详细介绍了每个类,以及API描述、功能和需要注意的地方,详细介绍了每个控件
|
||||||
|
|
||||||
|
- **窗口拉伸 / 最大化补强(在 EasyX 基础上用 Win32 加固)**
|
||||||
|
- 新增 `Window::enableResize(bool enable, int minW, int minH)`;运行时开关可拉伸并设置最小跟踪尺寸。
|
||||||
|
- 子类化窗口过程:处理 `WM_GETMINMAXINFO / WM_SIZE / WM_EXITSIZEMOVE / WM_ERASEBKGND / WM_PAINT`,并启用 `WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CLIPCHILDREN | WS_CLIPSIBLINGS`,解决 EasyX 窗口原生不可拉伸问题。
|
||||||
|
- **整窗背景绘制**:在 `WM_PAINT` 用 GDI 直写客户区(纯色/图片均支持),彻底消除黑边与最大化残影。
|
||||||
|
- **一次性重绘**:合并连续 `WM_SIZE`,使用 `pendingW/H + needResizeDirty` 在主循环尾部统一置脏重绘,避免死循环与抖动。
|
||||||
|
- **布局管理器(第一阶段)**
|
||||||
|
- 在 `Canvas` 引入布局元数据:`LayoutKind::{Absolute, HBox, VBox, Grid(预留), Flow(预留), Stack(预留)}` 与 `LayoutParams`(`margin{L,R,T,B}`、`fixedW/fixedH(-1=沿用)`、`weight`、`Align{Start,Center,End,Stretch}`)。
|
||||||
|
- 实装 **HBox/VBox** 自动布局(容器内部自动排布;容器本身仍绝对定位,可嵌套)。
|
||||||
|
- **选项卡(Tabs)控件雏形**
|
||||||
|
- 页签条与页面容器解耦;多页签切换;为透明主题提供**背景快照**避免叠影。
|
||||||
|
- **按钮文本单行截断(MBCS:中/英分治)**
|
||||||
|
- 基于像素宽度阈值的 `...` 截断,避免半字节/半汉字。
|
||||||
|
- **悬停提示(Tooltip)**
|
||||||
|
- 以 `Label` 为载体,支持延时出现、自动隐藏、自定义文案(默认回退到按钮文本);使用控件级**背景快照/恢复**。
|
||||||
|
|
||||||
|
### 🔧 变更
|
||||||
|
|
||||||
|
- **Window 渲染路径**
|
||||||
|
- 弱化“离屏 frame→整屏贴图”的依赖;在 `WM_PAINT` 走 **GDI 整窗背景 + EasyX 批量绘制**(`BeginBatchDraw/EndBatchDraw + FlushBatchDraw`)以抑制频闪。
|
||||||
|
- 尺寸变化仅重建 `zoomBackground`(图片背景的缩放副本),显示延后到下一帧。
|
||||||
|
- **Control 基类**
|
||||||
|
- 标准化 **captureBackground/restoreBackground**;统一透明/叠放控件行为。
|
||||||
|
- 统一 `dirty` 语义:容器自身脏才重画背景,但**子控件每帧评估并按需绘制**,不再出现“容器不脏→子控件不画”的空窗。
|
||||||
|
- **Table**
|
||||||
|
- 透明模式下,分页按钮与页码标签跟随表格的文本/填充风格,避免风格漂移。
|
||||||
|
- **分页度量与整体居中**重做:可用宽度含表头;“上一页/下一页+页码”作为**一个块**水平居中。
|
||||||
|
- 修复**背景快照区域**未计入表头导致恢复失败的问题。
|
||||||
|
- **事件循环**
|
||||||
|
- 合并 `WM_SIZE` 到循环尾统一处理,降低输入延迟与重绘风暴。
|
||||||
|
|
||||||
|
### ✅ 修复
|
||||||
|
|
||||||
|
- **黑边 / 最大化残影 / 频闪**:`WM_ERASEBKGND` 返回 1 禁止系统擦背景;`WM_PAINT` 全面覆盖;清空裁剪区防止旧帧残留。
|
||||||
|
- **容器不画、控件需交互后才出现**:首帧与输入后触发**全树置脏**;子控件在容器未脏时也能正常绘制。
|
||||||
|
- **Table 翻页重叠与透明叠影**:修正快照区域、重算坐标并即时恢复+重绘。
|
||||||
|
|
||||||
|
### ⚠️ 可能不兼容
|
||||||
|
|
||||||
|
- 若外部代码直接访问 `Window` 私有成员(如 `dialogs`),请改用 `getControls()` / `getdialogs()`。
|
||||||
|
- `Table` 的分页与表头度量变化可能影响外部硬编码偏移;需要对齐新公式。
|
||||||
|
- 自定义控件若未遵循绘制前 `SetWorkingImage(nullptr)` 的约定,请自查。
|
||||||
|
|
||||||
|
### 📌 升级指引
|
||||||
|
|
||||||
|
1. **窗口背景**:将手工 `cleardevice()+putimage` 迁移到 `WM_PAINT` 的**整窗覆盖**流程。
|
||||||
|
2. **透明控件**:首绘前 `captureBackground()`,隐藏/覆盖时 `restoreBackground()`。
|
||||||
|
3. **布局**:容器设 `layout.kind = HBox/VBox`;子项用 `LayoutParams`(`margin/fixed/weight/align`)。
|
||||||
|
4. **按钮截断**:保持单次截断,避免重复 `...`。
|
||||||
|
5. **表格**:移除自绘分页,使用内置导航。
|
||||||
|
|
||||||
|
## [v2.0.1] - 2025 - 10 - 03
|
||||||
|
|
||||||
|
### 新增
|
||||||
|
|
||||||
|
- 新增示例:基于 StellarX 实现的 32 位寄存器查看工具(支持位取反、左移、右移,十六进制/十进制带符号/无符号切换,分组二进制显示)。
|
||||||
|
- 示例路径:`examples/register-viewer/`
|
||||||
|
- `TextBox`新增`setText`API,可在外部设置文本框内容
|
||||||
|
- `TextBox::setText`API修改:在设置文本后立即调用`draw`方法重绘
|
||||||
|
- `Button`新增`setButtonClick`API,允许通过外部函数修改按钮的点击状态,并执行相应的回调函数
|
||||||
|
- ==所有文档更新对应英文版本==
|
||||||
|
|
||||||
|
## [v2.0.0] - 2025-09-21
|
||||||
|
|
||||||
|
### 概述
|
||||||
|
v2.0.0 为一次重大升级(major release)。本次发布新增对话框与消息框工厂(Dialog / MessageBox),并对事件分发、API语义与内部资源管理做了若干重要修复和改进。
|
||||||
|
|
||||||
|
部分 API/行为发生不向后兼容的变化(breaking changes)。
|
||||||
|
|
||||||
|
### 新增
|
||||||
|
- **对话框系统**:
|
||||||
|
- 新增 `Dialog` 类,继承自 `Canvas`,用于构建模态与非模态对话框
|
||||||
|
- 新增 `MessageBox` 工厂类,提供 `ShowModal`(同步阻塞)与 `ShowAsync`(异步回调)两种便捷API
|
||||||
|
- 支持六种标准消息框类型:`OK`, `OKCancel`, `YesNo`, `YesNoCancel`, `RetryCancel`, `AbortRetryIgnore`
|
||||||
|
- 自动处理对话框布局、背景保存与恢复、事件传播和生命周期管理
|
||||||
|
|
||||||
|
- **事件系统增强**:
|
||||||
|
- 所有控件的 `handleEvent` 方法现在返回 `bool` 类型,表示是否消费了事件
|
||||||
|
- 引入事件消费机制,支持更精细的事件传播控制
|
||||||
|
- Window 类的事件循环现在优先处理对话框事件
|
||||||
|
|
||||||
|
- **控件状态管理**:
|
||||||
|
- Control 基类新增 `dirty` 标志和 `setDirty()` 方法,统一管理重绘状态
|
||||||
|
- 所有控件现在都实现了 `IsVisible()` 和 `model()` 方法
|
||||||
|
|
||||||
|
- **API 增强**:
|
||||||
|
- Button 类新增 `setButtonFalseColor()` 方法
|
||||||
|
- TextBox 类的 `setMaxCharLen()` 现在接受 `size_t` 类型参数
|
||||||
|
- Window 类新增对话框管理方法和去重检测机制
|
||||||
|
|
||||||
|
### 重大变更(Breaking Changes)
|
||||||
|
- **API 签名变更**:
|
||||||
|
- 所有控件的 `handleEvent(const ExMessage& msg)` 方法从返回 `void` 改为返回 `bool`
|
||||||
|
- Control 基类新增纯虚函数 `IsVisible() const` 和 `model() const`,所有派生类必须实现
|
||||||
|
|
||||||
|
- **资源管理变更**:
|
||||||
|
- Control 基类的样式保存从栈对象改为堆对象,使用指针管理
|
||||||
|
- 增强了资源释放的安全性,所有资源都在析构函数中正确释放
|
||||||
|
|
||||||
|
- **事件处理逻辑**:
|
||||||
|
- Window 的 `runEventLoop()` 方法完全重写,现在优先处理对话框事件
|
||||||
|
- 引入了事件消费机制,事件被消费后不会继续传播
|
||||||
|
|
||||||
|
### 修复(Fixed)
|
||||||
|
- **内存管理**:
|
||||||
|
- 修复 `Button::setFillIma()` 的内存泄漏问题
|
||||||
|
- 修复 Control 基类析构函数中的资源释放问题
|
||||||
|
- 修复 Dialog 类背景图像资源管理问题
|
||||||
|
|
||||||
|
- **布局与渲染**:
|
||||||
|
- 修复 `Table` 组件的分页计算、列宽和行高越界问题
|
||||||
|
- 修复 `Table` 中 `pX` 坐标累加错误导致的布局错乱
|
||||||
|
- 修复 `Canvas::draw()` 中导致子控件不被绘制的 dirty 判定问题
|
||||||
|
- 修复 `TextBox::draw()` 和 `restoreStyle()` 的不对称调用问题
|
||||||
|
|
||||||
|
- **事件处理**:
|
||||||
|
- 修复窗口事件分发逻辑,确保对话框/顶层控件优先处理事件
|
||||||
|
- 修复鼠标移出按钮区域时状态更新不及时的问题
|
||||||
|
- 修复非模态对话框事件处理中的竞争条件
|
||||||
|
|
||||||
|
- **其他问题**:
|
||||||
|
- 修复文本测量和渲染中的潜在错误
|
||||||
|
- 修复对话框关闭后背景恢复不完全的问题
|
||||||
|
- 修复多对话框场景下的 z-order 管理问题
|
||||||
|
|
||||||
|
### 改进(Changed)
|
||||||
|
- **代码质量**:
|
||||||
|
- 重构了大量内部 API,增强异常安全性与可维护性
|
||||||
|
- 使用智能指针和现代 C++ 特性替代裸 new/delete
|
||||||
|
- 统一了代码风格和命名约定
|
||||||
|
|
||||||
|
- **性能优化**:
|
||||||
|
- 优化了事件处理流程,减少不必要的重绘
|
||||||
|
- 改进了对话框背景保存和恢复机制
|
||||||
|
- 减少了内存分配和拷贝操作
|
||||||
|
|
||||||
|
- **文档与示例**:
|
||||||
|
- 为所有新增功能添加了详细的使用示例
|
||||||
|
- 完善了代码注释和 API 文档
|
||||||
|
- 更新了 README 和 CHANGELOG 反映最新变化
|
||||||
|
|
||||||
|
## [1.1.0] - 2025-09-08
|
||||||
|
|
||||||
|
### 新增
|
||||||
|
- **Window 类 API 增强**:
|
||||||
|
- 添加了完整的获取器(getter)方法集,提高类的封装性和可用性
|
||||||
|
- `getHwnd()` - 获取窗口句柄,便于与原生 Windows API 集成
|
||||||
|
- `getWidth()` - 获取窗口宽度
|
||||||
|
- `getHeight()` - 获取窗口高度
|
||||||
|
- `getHeadline()` - 获取窗口标题
|
||||||
|
- `getBkcolor()` - 获取窗口背景颜色
|
||||||
|
- `getBkImage()` - 获取窗口背景图片指针
|
||||||
|
- `getControls()` - 获取控件管理容器的引用,允许迭代和操作已添加的控件
|
||||||
|
|
||||||
|
### 改进
|
||||||
|
- **API 一致性**: 为所有重要属性提供了对称的设置器(setter)和获取器(getter)方法
|
||||||
|
- **代码文档**: 进一步完善了类注释,使其更加清晰和专业
|
||||||
|
|
||||||
## [1.0.0] - 2025-09-08
|
## [1.0.0] - 2025-09-08
|
||||||
|
|
||||||
### 发布摘要
|
### 发布摘要
|
||||||
|
|||||||
137
CMakeLists.txt
137
CMakeLists.txt
@@ -1,130 +1,19 @@
|
|||||||
cmake_minimum_required(VERSION 3.15)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(StellarX VERSION 1.0.0 LANGUAGES CXX)
|
|
||||||
|
|
||||||
# 设置C++标准
|
# 项目定义
|
||||||
|
project(StellarX VERSION 2.0.0 LANGUAGES CXX)
|
||||||
|
|
||||||
|
# 设置 C++ 标准
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
||||||
|
|
||||||
# Windows特定设置
|
# 包含头文件目录(目前头文件都在根目录)
|
||||||
if(WIN32)
|
include_directories(${CMAKE_SOURCE_DIR})
|
||||||
add_definitions(-DWIN32 -D_WINDOWS)
|
|
||||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
|
||||||
|
|
||||||
# 设置Windows子系统
|
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SUBSYSTEM:WINDOWS")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# 设置输出目录
|
# 源文件收集
|
||||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
file(GLOB_RECURSE SOURCES
|
||||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
"${CMAKE_SOURCE_DIR}/*.cpp"
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
|
||||||
|
|
||||||
# 包含目录
|
|
||||||
include_directories(include)
|
|
||||||
|
|
||||||
# Windows API库
|
|
||||||
if(WIN32)
|
|
||||||
find_library(GDI32_LIBRARY gdi32)
|
|
||||||
find_library(USER32_LIBRARY user32)
|
|
||||||
find_library(KERNEL32_LIBRARY kernel32)
|
|
||||||
set(WIN32_LIBS ${GDI32_LIBRARY} ${USER32_LIBRARY} ${KERNEL32_LIBRARY})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# 创建库
|
|
||||||
file(GLOB_RECURSE SOURCES "src/*.cpp")
|
|
||||||
add_library(StellarX STATIC ${SOURCES})
|
|
||||||
|
|
||||||
# 链接Windows库
|
|
||||||
if(WIN32)
|
|
||||||
target_link_libraries(StellarX ${WIN32_LIBS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# 设置库属性
|
|
||||||
set_target_properties(StellarX PROPERTIES
|
|
||||||
VERSION ${PROJECT_VERSION}
|
|
||||||
SOVERSION 1
|
|
||||||
PUBLIC_HEADER "include/StellarX/StellarX.h"
|
|
||||||
OUTPUT_NAME "StellarX"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# 安装规则
|
# 生成可执行文件
|
||||||
install(DIRECTORY include/StellarX DESTINATION include)
|
add_executable(StellarX ${SOURCES})
|
||||||
install(TARGETS StellarX
|
|
||||||
ARCHIVE DESTINATION lib
|
|
||||||
LIBRARY DESTINATION lib
|
|
||||||
RUNTIME DESTINATION bin
|
|
||||||
PUBLIC_HEADER DESTINATION include/StellarX
|
|
||||||
)
|
|
||||||
|
|
||||||
# 示例程序
|
|
||||||
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/examples)
|
|
||||||
file(GLOB EXAMPLE_SOURCES "examples/*.cpp")
|
|
||||||
foreach(example_source ${EXAMPLE_SOURCES})
|
|
||||||
get_filename_component(example_name ${example_source} NAME_WE)
|
|
||||||
add_executable(${example_name} ${example_source})
|
|
||||||
target_link_libraries(${example_name} StellarX ${WIN32_LIBS})
|
|
||||||
|
|
||||||
# 设置Windows子系统
|
|
||||||
if(WIN32)
|
|
||||||
set_target_properties(${example_name} PROPERTIES
|
|
||||||
LINK_FLAGS "/SUBSYSTEM:WINDOWS"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# 安装示例
|
|
||||||
install(TARGETS ${example_name} DESTINATION bin)
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# 文档生成选项
|
|
||||||
option(BUILD_DOCS "Build documentation" OFF)
|
|
||||||
if(BUILD_DOCS)
|
|
||||||
find_package(Doxygen)
|
|
||||||
if(Doxygen_FOUND)
|
|
||||||
set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/docs)
|
|
||||||
set(DOXYGEN_USE_MDFILE_AS_MAINPAGE ${CMAKE_CURRENT_SOURCE_DIR}/README.md)
|
|
||||||
set(DOXYGEN_PROJECT_NAME "StellarX GUI Framework")
|
|
||||||
set(DOXYGEN_PROJECT_NUMBER ${PROJECT_VERSION})
|
|
||||||
set(DOXYGEN_PROJECT_BRIEF "A lightweight, modular C++ GUI framework for Windows")
|
|
||||||
set(DOXYGEN_INPUT ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
|
||||||
set(DOXYGEN_RECURSIVE YES)
|
|
||||||
set(DOXYGEN_EXTRACT_ALL YES)
|
|
||||||
set(DOXYGEN_EXTRACT_PRIVATE YES)
|
|
||||||
set(DOXYGEN_EXTRACT_STATIC YES)
|
|
||||||
set(DOXYGEN_SOURCE_BROWSER YES)
|
|
||||||
set(DOXYGEN_GENERATE_TREEVIEW YES)
|
|
||||||
set(DOXYGEN_HAVE_DOT YES)
|
|
||||||
set(DOXYGEN_CALL_GRAPH YES)
|
|
||||||
set(DOXYGEN_CALLER_GRAPH YES)
|
|
||||||
|
|
||||||
doxygen_add_docs(
|
|
||||||
docs
|
|
||||||
${DOXYGEN_INPUT}
|
|
||||||
COMMENT "Generate HTML documentation"
|
|
||||||
)
|
|
||||||
|
|
||||||
install(DIRECTORY ${DOXYGEN_OUTPUT_DIRECTORY}/html DESTINATION docs)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# 打包配置
|
|
||||||
include(InstallRequiredSystemLibraries)
|
|
||||||
set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
|
|
||||||
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
|
|
||||||
set(CPACK_PACKAGE_DESCRIPTION "A lightweight, modular C++ GUI framework for Windows")
|
|
||||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "星垣 (StellarX) - A lightweight Windows GUI framework")
|
|
||||||
set(CPACK_PACKAGE_VENDOR "StellarX Development Team")
|
|
||||||
set(CPACK_PACKAGE_CONTACT "contact@stellarx.dev")
|
|
||||||
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
|
|
||||||
set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
|
|
||||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY "StellarX")
|
|
||||||
set(CPACK_NSIS_MODIFY_PATH ON)
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
set(CPACK_GENERATOR "ZIP;NSIS")
|
|
||||||
else()
|
|
||||||
set(CPACK_GENERATOR "ZIP")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
include(CPack)
|
|
||||||
|
|||||||
94
CONTRIBUTING.en.md
Normal file
94
CONTRIBUTING.en.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# Contributing to StellarX
|
||||||
|
|
||||||
|
Thank you for your interest in contributing to StellarX! This document provides guidelines and steps for contributing.
|
||||||
|
|
||||||
|
StellarX is a C++ GUI framework built for the **Windows platform**, based on the EasyX graphics library.
|
||||||
|
|
||||||
|
## Development Environment Setup
|
||||||
|
|
||||||
|
1. Install Visual Studio 2019 or later
|
||||||
|
2. Install the corresponding version of EasyX graphics library
|
||||||
|
3. Install CMake 3.12 or later
|
||||||
|
4. Clone the project repository
|
||||||
|
5. Use CMake to generate the solution and compile
|
||||||
|
|
||||||
|
## How to Contribute
|
||||||
|
|
||||||
|
### Reporting Bugs
|
||||||
|
1. Check [Issues](../../issues) to see if the bug has already been reported.
|
||||||
|
2. If not, create a new Issue.
|
||||||
|
3. Use the "Bug Report" template.
|
||||||
|
4. Provide a **clear title and description**.
|
||||||
|
5. Include relevant code snippets, screenshots, or steps to reproduce the issue.
|
||||||
|
|
||||||
|
### Suggesting Enhancements
|
||||||
|
1. Check existing Issues to see if your idea has been suggested.
|
||||||
|
2. Create a new Issue using the "Feature Request" template.
|
||||||
|
3. Clearly describe the new feature and explain why it would be useful.
|
||||||
|
|
||||||
|
### Submitting Code Changes (Pull Requests)
|
||||||
|
1. **Fork** the repository on GitHub.
|
||||||
|
2. **Clone** your forked repository to your local machine.
|
||||||
|
3. Create a **new branch** for your feature or bug fix (`git checkout -b my-feature-branch`).
|
||||||
|
4. **Make your changes**. Ensure your code follows the project's style (see below).
|
||||||
|
5. **Commit your changes** with clear, descriptive commit messages.
|
||||||
|
6. **Push** your branch to your forked GitHub repository (`git push origin my-feature-branch`).
|
||||||
|
7. Open a **Pull Request** against the original StellarX repository's `main` branch.
|
||||||
|
|
||||||
|
## Code Style Guide
|
||||||
|
|
||||||
|
* Follow the existing code formatting and naming conventions in the project.
|
||||||
|
* Use meaningful names for variables, functions, and classes.
|
||||||
|
* Comment your code when necessary, especially for complex logic.
|
||||||
|
* Ensure your code compiles without warnings.
|
||||||
|
* Test your changes thoroughly.
|
||||||
|
* Use **4 spaces** for indentation (no tabs)
|
||||||
|
* Class names use **PascalCase** (e.g., `ClassName`)
|
||||||
|
* Functions and variables use **camelCase** (e.g., `functionName`, `variableName`)
|
||||||
|
* Constants use **UPPER_CASE** (e.g., `CONSTANT_VALUE`)
|
||||||
|
* Member variables use **m_** prefix (e.g., `m_memberVariable`)
|
||||||
|
* Use meaningful names for control properties, avoid abbreviations
|
||||||
|
* Add detailed comments for all public interfaces
|
||||||
|
* Follow RAII principles for resource management
|
||||||
|
|
||||||
|
## Example Code Style
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// Good Example
|
||||||
|
class MyControl : public Control {
|
||||||
|
public:
|
||||||
|
MyControl(int x, int y, int width, int height)
|
||||||
|
: Control(x, y, width, height), m_isActive(false) {}
|
||||||
|
|
||||||
|
void draw() override {
|
||||||
|
// Drawing logic
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_isActive;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bad Example
|
||||||
|
class my_control: public Control{
|
||||||
|
public:
|
||||||
|
my_control(int x,int y,int w,int h):Control(x,y,w,h),active(false){}
|
||||||
|
void Draw() override{
|
||||||
|
// Drawing logic
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
bool active;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
Please follow the project's directory structure:
|
||||||
|
|
||||||
|
- Header files go in the `include/StellarX/` directory
|
||||||
|
- Implementation files go in the `src/` directory
|
||||||
|
- Example code goes in the `examples/` directory
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
If you have any questions, feel free to open an Issue or contact the maintainers.
|
||||||
|
|
||||||
309
README.en.md
Normal file
309
README.en.md
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
# StellarX GUI Framework README
|
||||||
|
|
||||||
|
[中文README](README.md)
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|

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

|
||||||
|

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

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

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

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

|
||||||
|
|
||||||
|
For details, please refer to the [CHANGELOG.en](CHANGELOG.en.md).
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
## 📦 Project Structure & Design Philosophy
|
||||||
|
|
||||||
|
StellarX adopts classic **OOP** and **modular** design with a clear structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
StellarX/
|
||||||
|
├── include/
|
||||||
|
│ └── StellarX/
|
||||||
|
│ ├── StellarX.h
|
||||||
|
│ ├── 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
|
||||||
|
│ └── Table.h
|
||||||
|
├── src/
|
||||||
|
│ ├── Control.cpp
|
||||||
|
│ ├── Button.cpp
|
||||||
|
│ ├── Window.cpp
|
||||||
|
│ ├── Label.cpp
|
||||||
|
│ ├── TextBox.cpp
|
||||||
|
│ ├── Canvas.cpp
|
||||||
|
│ ├── TabControl.cpp #v2.2.0
|
||||||
|
│ ├── Table.cpp
|
||||||
|
│ ├── Dialog.cpp
|
||||||
|
│ └── MessageBox.cpp
|
||||||
|
├── examples/
|
||||||
|
│ └── demo.cpp
|
||||||
|
├── docs/
|
||||||
|
│ └── CODE_OF_CONDUCT.md
|
||||||
|
├── CMakeLists.txt
|
||||||
|
├── CONTRIBUTING.md
|
||||||
|
├── CHANGELOG.md
|
||||||
|
├── CHANGELOG.en.md
|
||||||
|
├── Doxyfile
|
||||||
|
├── LICENSE
|
||||||
|
├──API 文档.md
|
||||||
|
├──API Documentation.en.md
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**Design Philosophy:**
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
- **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)
|
||||||
|
|
||||||
|
> Get the prebuilt package from [Releases](https://github.com/Ysm-04/StellarX/releases/latest).
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- **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 the latest EasyX
|
||||||
|
2. Install components matching your Visual Studio version
|
||||||
|
3. The framework links automatically—no extra config needed
|
||||||
|
|
||||||
|
### Build with CMake (recommended)
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/Ysm-04/StellarX.git
|
||||||
|
cd StellarX
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake ..
|
||||||
|
cmake --build .
|
||||||
|
./examples/Demo
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Integration
|
||||||
|
|
||||||
|
- Copy `include` and `src`
|
||||||
|
- Add header search path: `include/StellarX/`
|
||||||
|
- Add all `.cpp` files to your project
|
||||||
|
|
||||||
|
### First Resizable Window
|
||||||
|
|
||||||
|
```
|
||||||
|
#include "StellarX.h"
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
// Add your controls...
|
||||||
|
// mainWindow.addControl(std::move(btn));
|
||||||
|
|
||||||
|
mainWindow.runEventLoop();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> 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 (excerpt from `CoreTypes.h`)
|
||||||
|
|
||||||
|
### Enums
|
||||||
|
|
||||||
|
| 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` |
|
||||||
|
|
||||||
|
| Enum | Description | Common values |
|
||||||
|
| ------------ | ---------------------- | -------------------------------------------- |
|
||||||
|
| `LayoutMode` | 窗口布局模式 | `Fixed`, `AnchorToEdges` |
|
||||||
|
| `Anchor` | 控件相对于父容器的锚点 | `NoAnchor` ,`Left` , `Right`, `Top`,`Bottom` |
|
||||||
|
|
||||||
|
### Structs
|
||||||
|
|
||||||
|
| Struct | Description |
|
||||||
|
| -------------- | ---------------------------------------------------- |
|
||||||
|
| `ControlText` | Font/size/color/bold/italic/underline/strike-through |
|
||||||
|
| `RouRectangle` | Corner ellipse size for rounded rectangles |
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
## 🧩 Controls Library
|
||||||
|
|
||||||
|
### 1) Basic Controls
|
||||||
|
|
||||||
|
| Control | Header | Description | Key Points |
|
||||||
|
| ------- | ----------- | ----------------- | ------------------------------------------------------------ |
|
||||||
|
| 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) Container Controls
|
||||||
|
|
||||||
|
| Control | Header | Description |
|
||||||
|
| ------- | ---------- | ------------------------------------------------------------ |
|
||||||
|
| 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 Controls
|
||||||
|
|
||||||
|
| Control | Header | Description | Key Points |
|
||||||
|
| ---------- | -------------- | ----------- | ------------------------------------------------------------ |
|
||||||
|
| 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` | Message-box factory | Static API; modal/non-modal; de-dup built in |
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
## 📐 Layout Management (HBox/VBox)
|
||||||
|
|
||||||
|
==Reserved, to be implemented==
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
## 🗂 Tabs (TabControl)
|
||||||
|
|
||||||
|
- 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 Text Truncation & Button Tooltip
|
||||||
|
|
||||||
|
- **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 Snapshots
|
||||||
|
|
||||||
|
- **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 from `Control`, implement `draw()` / `handleEvent()`
|
||||||
|
- Performance:
|
||||||
|
- **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
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
## ⚠️ Applicability & Limits
|
||||||
|
|
||||||
|
- 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 (see `LICENSE`).
|
||||||
|
|
||||||
|
## 👥 Contributing Guidelines
|
||||||
|
|
||||||
|
- 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 **simplicity/efficiency/clarity**
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
**Stars and seas, code as the vessel.**
|
||||||
|
|
||||||
|
## 📞 Support & Feedback
|
||||||
|
|
||||||
|
- See [examples/](examples/)
|
||||||
|
- Read the [CHANGELOG](CHANGELOG.md / CHANGELOG.en.md)
|
||||||
|
- Submit an Issue on GitHub
|
||||||
477
README.md
477
README.md
@@ -1,7 +1,19 @@
|
|||||||
# 星垣 (StellarX) GUI Framework
|
# 星垣 (StellarX) GUI Framework
|
||||||
|
|
||||||
 <!-- 新增版本徽章 -->
|
[English document](README.en.md)
|
||||||
 <!-- 新增下载徽章 -->
|
|
||||||
|
> 本仓库为 **StellarX** 主仓:开发与 Issue/PR 均在 GitHub 进行。
|
||||||
|
> GitCode 仅为只读镜像:如需反馈请到 GitHub:https://github.com/Ysm-04/StellarX
|
||||||
|
|
||||||
|
[](https://gitcode.com/Ysm-04/StellarX)
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|

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

|
||||||
|

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

|

|
||||||

|

|
||||||
@@ -10,334 +22,297 @@
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
> **「繁星为界,轻若尘埃」** —— 一个为Windows平台打造的、极致轻量级、高度模块化的C++原生GUI框架。
|
> **「繁星为界,轻若尘埃」** —— 一个为 Windows 平台打造的、极致轻量级、高度模块化的 C++ 原生 GUI 框架。
|
||||||
|
|
||||||
`星垣 (StellarX)` 诞生于对现代GUI框架"过度臃肿"的反抗。它拒绝动辄数百MB的依赖、漫长的编译时间和复杂的学习曲线,选择回归本质:用最精简的代码、最清晰的架构和最高的效率,解决桌面应用开发的核心需求。
|
`星垣 (StellarX)` 反对臃肿,拒绝动辄数百 MB 的依赖、漫长编译与高门槛学习曲线,回归本质:以精简代码、清晰架构与高效率,解决桌面应用开发的核心需求。
|
||||||
|
|
||||||
它是一个**纯粹的教学级、工具级框架**,旨在让开发者深入理解GUI原理,并快速构建轻量级Windows工具。
|
这是一个**教学级、工具级**框架,帮助开发者深入理解 GUI 原理,并快速构建轻量工具。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🆕V2.3.0——重要更新
|
||||||
|
|
||||||
|
**本版本是一次重大更新,增加了响应式布局系统,由静态布局转变为动态布局,并且彻底修复了之前存在的由重入重绘导致的概率出现的渲染错乱问题**
|
||||||
|
|
||||||
|
- **优化窗口尺寸调节机制**:重构 `WndProcThunk`、`runEventLoop` 和 `pumpResizeIfNeeded`,统一记录尺寸变化并在事件循环末尾集中重绘,避免重复重绘导致的抖动和顺序错乱。
|
||||||
|
|
||||||
|
- **新增对话框尺寸调度接口**:引入 `Window::scheduleResizeFromModal()` 与 `pumpResizeIfNeeded()` 的组合,模态对话框在拉伸期间也可通知父窗口更新尺寸。底层控件将在统一收口时重新布局,而对话框自身保持尺寸不变。
|
||||||
|
|
||||||
|
- **自适应布局改进**:内部新增 `adaptiveLayout()` 函数,按照锚点重新计算控件位置和尺寸,使双锚定(左右或上下)控件随窗口变化自适应伸缩。
|
||||||
|
|
||||||
|
- **修复模态对话框拉伸问题**:解决模态对话框打开时,窗口拉伸导致底层控件无法根据锚点更新的位置和尺寸的问题;同时避免对话框反复重绘导致的残影。
|
||||||
|
|
||||||
|
- **进一步解决绘制顺序错乱**:拉伸过程中采用 `ValidateRect` 替代 `InvalidateRect`,确保窗口仅在一次统一收口绘制后标记为有效,杜绝系统再次触发 `WM_PAINT` 造成重入。
|
||||||
|
|
||||||
|
- 其他修复:修正表格和对话框背景快照某些边界情况下的更新不及时问题。
|
||||||
|
|
||||||
|

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

|
||||||
|
|
||||||
|
详细变更请参阅[更新日志](CHANGELOG.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📦 项目结构与设计哲学
|
## 📦 项目结构与设计哲学
|
||||||
|
|
||||||
星垣框架采用经典的**面向对象**和**模块化**设计,项目结构清晰规范:
|
星垣采用经典 **OOP** 与 **模块化** 设计,结构清晰:
|
||||||
|
|
||||||
```
|
```markdown
|
||||||
StellarX/
|
StellarX/
|
||||||
├── include/ # 头文件目录
|
├── include/
|
||||||
│ └── StellarX/ # 框架头文件
|
│ └── StellarX/
|
||||||
│ ├── StellarX.h # 主包含头文件 - 一键引入整个框架
|
│ ├── StellarX.h
|
||||||
│ ├── CoreTypes.h # ★ 核心 ★ - 所有枚举、结构体的唯一定义源
|
│ ├── CoreTypes.h # 唯一定义源(枚举/结构体)
|
||||||
│ ├── Control.h # 抽象基类 - 定义所有控件的统一接口
|
│ ├── Control.h
|
||||||
│ ├── Button.h # 按钮控件
|
│ ├── Button.h
|
||||||
│ ├── Window.h # 窗口管理
|
│ ├── Window.h
|
||||||
│ ├── Label.h # 标签控件
|
│ ├── Label.h
|
||||||
│ ├── TextBox.h # 文本框控件
|
│ ├── TextBox.h
|
||||||
│ ├── Canvas.h # 画布容器
|
│ ├── TabControl.h #v2.2.0
|
||||||
│ └── Table.h # 表格控件
|
│ ├── Canvas.h
|
||||||
├── src/ # 源文件目录
|
│ ├── Dialog.h
|
||||||
|
│ ├── MessageBox.h
|
||||||
|
│ └── Table.h
|
||||||
|
├── src/
|
||||||
│ ├── Control.cpp
|
│ ├── Control.cpp
|
||||||
│ ├── Button.cpp
|
│ ├── Button.cpp
|
||||||
│ ├── Window.cpp
|
│ ├── Window.cpp
|
||||||
│ ├── Label.cpp
|
│ ├── Label.cpp
|
||||||
│ ├── TextBox.cpp
|
│ ├── TextBox.cpp
|
||||||
│ ├── Canvas.cpp
|
│ ├── Canvas.cpp
|
||||||
│ └── Table.cpp
|
│ ├── TabControl.cpp #v2.2.0
|
||||||
├── examples/ # 示例代码目录
|
│ ├── Table.cpp
|
||||||
│ └── demo.cpp # 基础演示
|
│ ├── Dialog.cpp
|
||||||
├── docs/ # 文档目录
|
│ └── MessageBox.cpp
|
||||||
│ └── CODE_OF_CONDUCT.md # 行为准则
|
├── examples/
|
||||||
├── CMakeLists.txt # CMake 构建配置
|
│ └── demo.cpp
|
||||||
├── CONTRIBUTING.md # 贡献指南
|
├── docs/
|
||||||
├── CHANGELOG.md # 更新日志
|
│ └── CODE_OF_CONDUCT.md
|
||||||
├── Doxyfile # Doxygen 配置
|
├── CMakeLists.txt
|
||||||
├── LICENSE # MIT 许可证
|
├── CONTRIBUTING.md
|
||||||
└── README.md # 项目说明
|
├── CHANGELOG.md
|
||||||
|
├── CHANGELOG.en.md
|
||||||
|
├── Doxyfile
|
||||||
|
├── LICENSE
|
||||||
|
├──API 文档.md
|
||||||
|
├──API Documentation.en.md
|
||||||
|
└── README.md
|
||||||
```
|
```
|
||||||
|
|
||||||
**设计理念:**
|
**设计理念:**
|
||||||
1. **单一职责原则 (SRP)**: 每个类/文件只负责一件事。
|
|
||||||
2. **依赖倒置原则 (DIP)**: 高层模块(如`Window`)不依赖低层模块(如`Button`),二者都依赖其抽象(`Control`)。
|
1. **单一职责(SRP)**:每个类/文件只做一件事。
|
||||||
3. **开闭原则 (OCP)**: 通过继承`Control`基类,可以轻松扩展新的控件,而无需修改现有代码。
|
2. **依赖倒置(DIP)**:高层模块依赖抽象(`Control`),而非具体控件。
|
||||||
4. **一致性**: 所有控件共享统一的`draw()`和`handleEvent()`接口。
|
3. **开闭原则(OCP)**:继承 `Control` 可扩展新控件,无需修改既有代码。
|
||||||
|
4. **一致性**:所有控件统一 `draw()` / `handleEvent()` 接口。
|
||||||
|
|
||||||
## 🚀 核心特性
|
## 🚀 核心特性
|
||||||
|
|
||||||
- **极致的轻量级**: 核心库编译后仅 ~1.2MB,无任何外部依赖。生成的应用程序小巧玲珑。
|
- **极致轻量**:除 EasyX 外无外部重量级依赖。
|
||||||
- **清晰的模块化架构**: 使用`CoreTypes.h`统一管理所有类型,消除重复定义,极大提升可维护性。
|
- **模块清晰**:`CoreTypes.h` 统一类型与枚举。
|
||||||
- **原生C++性能**: 直接基于EasyX和Win32 API,提供接近原生的执行效率,内存占用极低(通常<10MB)。
|
- **原生性能**:EasyX + Win32,执行高效、内存低占用(常见 <10MB)。
|
||||||
- **丰富的控件库**: 提供按钮、标签、文本框、表格、画布等常用控件,满足基本桌面应用需求。
|
- **控件齐全**:Button、Label、TextBox、Canvas、Table、Dialog、MessageBox、**TabControl**。
|
||||||
- **高度可定制化**: 从控件颜色、形状(矩形、圆角、圆形、椭圆)到填充模式、字体样式,均有详尽枚举支持,可轻松定制。
|
- **高度自定义**:颜色、形状(矩形/圆角/圆/椭圆)、填充、字体等皆有枚举配置,易于切换。
|
||||||
- **简洁直观的API**: 采用经典的面向对象设计,代码即文档,学习成本极低。
|
- **简单直观 API**:OOP 设计,接口语义明确、调用友好,代码即文档。
|
||||||
- **标准项目结构**: 采用标准的include/src分离结构,支持CMake构建,易于集成和使用。
|
- **标准工程结构**:include/src 分离,支持 CMake 构建,方便集成到现有项目或开箱即用。
|
||||||
|
|
||||||
## ⚡ 快速开始(5分钟上手)
|
------
|
||||||
|
|
||||||
> **🎯 最新版本下载**
|
## ⚡ 快速开始(5 分钟上手)
|
||||||
> 从 [GitHub Releases](https://github.com/Ysm-04/StellarX/releases/latest) 下载预编译的库文件和头文件,即可快速集成到你的项目中。
|
|
||||||
|
> 从 [Releases](https://github.com/Ysm-04/StellarX/releases/latest) 获取预编译包。
|
||||||
|
|
||||||
### 环境要求
|
### 环境要求
|
||||||
|
|
||||||
- **操作系统**: Windows 10 或更高版本
|
- **系统**:Windows 10+
|
||||||
- **编译器**: 支持C++17的编译器 (如: **Visual Studio 2019+**)
|
- **编译器**:C++17(如 VS 2019+)
|
||||||
- **图形库**: [EasyX](https://easyx.cn/) (2022版本或更高,安装时请选择与您编译器匹配的版本)
|
- **图形库**: [EasyX](https://easyx.cn/) 2022+(与编译器匹配)
|
||||||
- **构建工具**: CMake 3.12+ (可选,推荐使用)
|
- **构建**:CMake 3.12+(可选)
|
||||||
|
|
||||||
### 安装 EasyX
|
### 安装 EasyX
|
||||||
1. 访问 [EasyX 官网](https://easyx.cn/) 下载最新版本
|
|
||||||
2. 运行安装程序,选择与您的 Visual Studio 版本匹配的版本
|
|
||||||
3. 安装完成后,无需额外配置,星垣框架会自动链接 EasyX
|
|
||||||
|
|
||||||
### 方法一:使用CMake构建(推荐)
|
1. 下载 EasyX 最新版
|
||||||
|
2. 按 VS 版本安装匹配组件
|
||||||
|
3. 框架会自动链接,无需额外配置
|
||||||
|
|
||||||
1. **克隆项目**:
|
### CMake 构建(推荐)
|
||||||
```bash
|
|
||||||
git clone https://github.com/Ysm-04/StellarX.git
|
|
||||||
cd StellarX
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **生成构建系统**:
|
```bash
|
||||||
```bash
|
git clone https://github.com/Ysm-04/StellarX.git
|
||||||
mkdir build
|
cd StellarX
|
||||||
cd build
|
mkdir build && cd build
|
||||||
cmake ..
|
cmake ..
|
||||||
```
|
cmake --build .
|
||||||
|
./examples/Demo
|
||||||
|
```
|
||||||
|
|
||||||
3. **编译项目**:
|
### 手动集成
|
||||||
```bash
|
|
||||||
cmake --build .
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **运行示例**:
|
- 拷贝 `include` 与 `src`
|
||||||
```bash
|
- 配置头文件搜索路径:`include/StellarX/`
|
||||||
./examples/Demo
|
- 将全部 `.cpp` 加入工程
|
||||||
```
|
|
||||||
|
|
||||||
### 方法二:手动集成到现有项目
|
### 第一个可拉伸窗口
|
||||||
|
|
||||||
1. **将include和src目录复制**到您的项目中
|
```c++
|
||||||
2. **配置包含路径**,确保编译器可以找到`include/StellarX/`目录
|
|
||||||
3. **将所有.cpp文件**添加到您的项目中编译
|
|
||||||
|
|
||||||
### 创建你的第一个星垣应用
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// 只需包含这一个头文件即可使用所有功能
|
|
||||||
#include "StellarX.h"
|
#include "StellarX.h"
|
||||||
|
|
||||||
// 程序入口点(请使用WinMain以获得更好的兼容性)
|
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
|
||||||
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd) {
|
{
|
||||||
|
//默认启动拉伸,当前尺寸为最小尺寸
|
||||||
|
Window mainWindow(800, 600, 0, RGB(255,255,255), "我的星垣应用");
|
||||||
|
mainWindow.draw();
|
||||||
|
|
||||||
// 1. 创建一个640x480的窗口,背景为白色,标题为"我的应用"
|
// 添加你的控件...
|
||||||
Window mainWindow(640, 480, 0, RGB(255, 255, 255), "我的第一个星垣应用");
|
// mainWindow.addControl(std::move(btn));
|
||||||
|
|
||||||
// 2. 创建一个按钮 (使用智能指针管理)
|
mainWindow.runEventLoop();
|
||||||
auto myButton = std::make_unique<Button>(
|
return 0;
|
||||||
250, 200, 140, 40, // x, y, 宽度, 高度
|
|
||||||
"点击我", // 按钮文本
|
|
||||||
StellarX::ButtonMode::NORMAL,
|
|
||||||
StellarX::ControlShape::ROUND_RECTANGLE
|
|
||||||
);
|
|
||||||
|
|
||||||
// 3. 为按钮设置点击事件(使用Lambda表达式)
|
|
||||||
myButton->setOnClickListener([]() {
|
|
||||||
MessageBox(nullptr, "Hello, 星垣!", "问候", MB_OK | MB_ICONINFORMATION);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 4. (可选)设置按钮样式
|
|
||||||
myButton->textStyle.nHeight = 20;
|
|
||||||
myButton->textStyle.color = RGB(0, 0, 128); // 深蓝色文字
|
|
||||||
myButton->setButtonBorder(RGB(0, 128, 255)); // 蓝色边框
|
|
||||||
|
|
||||||
// 5. 将按钮添加到窗口
|
|
||||||
mainWindow.addControl(std::move(myButton));
|
|
||||||
|
|
||||||
// 6. 绘制窗口
|
|
||||||
mainWindow.draw();
|
|
||||||
|
|
||||||
// 7. 进入消息循环,等待用户交互
|
|
||||||
mainWindow.runEventLoop();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
5. **编译并运行!** 您将看到一个带有蓝色圆角按钮的窗口,点击它将会弹出消息框。
|
> 实现要点:在 `WM_PAINT` 期进行**整窗背景绘制**(纯色/图片),并配合 EasyX 批量绘制抑制频闪与黑边。
|
||||||
|
|
||||||
## 📚 核心类型详解 (`CoreTypes.h`)
|
------
|
||||||
|
|
||||||
星垣框架的所有视觉和行为属性都通过`CoreTypes.h`中定义的精美枚举和结构体来控制。
|
## 📚 核心类型(`CoreTypes.h` 摘要)
|
||||||
|
|
||||||
### 枚举类型 (Enums)
|
### 枚举
|
||||||
|
|
||||||
| 枚举类型 | 描述 | 常用值 |
|
| 枚举 | 描述 | 常用值 |
|
||||||
| :----------------- | :----------- | :----------------------------------------------------------- |
|
| ------------------ | ---------- | ------------------------------------------------------------ |
|
||||||
| **`ControlShape`** | 控件几何形状 | `RECTANGLE`, `B_RECTANGLE`, `ROUND_RECTANGLE`, `CIRCLE`, `ELLIPSE`等 |
|
| `ControlShape` | 几何形状 | `RECTANGLE`, `B_RECTANGLE`, `ROUND_RECTANGLE`, `CIRCLE`, `ELLIPSE` |
|
||||||
| **`ButtonMode`** | 按钮行为模式 | `NORMAL`(普通), `TOGGLE`(切换), `DISABLED`(禁用) |
|
| `ButtonMode` | 按钮行为 | `NORMAL`, `TOGGLE`, `DISABLED` |
|
||||||
| **`TextBoxMode`** | 文本框模式 | `INPUT_MODE`(输入), `READONLY_MODE`(只读) |
|
| `TextBoxMode` | 文本框模式 | `INPUT_MODE`, `READONLY_MODE` |
|
||||||
| **`FillMode`** | 图形填充模式 | `SOLID`(实心), `NULL`(空心), `HATCHED`(图案)等 |
|
| `FillMode` | 填充模式 | `SOLID`, `NULL`, `HATCHED` |
|
||||||
| **`FillStyle`** | 图案填充样式 | `HORIZONTAL`(水平线), `CROSS`(十字线)等 |
|
| `FillStyle` | 图案样式 | `HORIZONTAL`, `CROSS` |
|
||||||
| **`LineStyle`** | 边框线型 | `SOLID`(实线), `DASH`(虚线), `DOT`(点线)等 |
|
| `LineStyle` | 线型 | `SOLID`, `DASH`, `DOT` |
|
||||||
|
| `MessageBoxType` | 消息框类型 | `OK`, `OKCancel`, `YesNo`, ... |
|
||||||
|
| `MessageBoxResult` | 结果 | `OK`, `Cancel`, `Yes`, `No`, `Abort`, `Retry`, `Ignore` |
|
||||||
|
| `TabPlacement` | 页签位置 | `Top`,`Bottom`,`Left`,`Right` |
|
||||||
|
|
||||||
### 结构体 (Structs)
|
| 枚举 | 描述 | 常用值 |
|
||||||
|
| ------------ | ---------------------- | -------------------------------------------- |
|
||||||
|
| `LayoutMode` | 窗口布局模式 | `Fixed`, `AnchorToEdges` |
|
||||||
|
| Anchor | 控件相对于父容器的锚点 | `NoAnchor` ,`Left` , `Right`, `Top`,`Bottom` |
|
||||||
|
|
||||||
| 结构体 | 描述 |
|
|
||||||
| :---------------- | :----------------------------------------------------------- |
|
|
||||||
| **`ControlText`** | 封装了所有文本样式属性,包括字体、大小、颜色、粗体、斜体、下划线、删除线等。 |
|
|
||||||
|
|
||||||
**使用示例:**
|
|
||||||
```cpp
|
|
||||||
// 创建一个复杂的文本样式
|
|
||||||
StellarX::ControlText myStyle;
|
|
||||||
myStyle.nHeight = 25; // 字体高度
|
|
||||||
myStyle.lpszFace = _T("微软雅黑"); // 字体
|
|
||||||
myStyle.color = RGB(255, 0, 0); // 红色
|
|
||||||
myStyle.nWeight = FW_BOLD; // 粗体
|
|
||||||
myStyle.bUnderline = true; // 下划线
|
|
||||||
|
|
||||||
// 应用于控件
|
### 结构体
|
||||||
myLabel->textStyle = myStyle;
|
|
||||||
myButton->textStyle = myStyle;
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🧩 控件库大全
|
| 结构体 | 描述 |
|
||||||
|
| -------------- | -------------------------------------- |
|
||||||
|
| `ControlText` | 字体/字号/颜色/粗体/斜体/下划线/删除线 |
|
||||||
|
| `RouRectangle` | 圆角矩形的角部椭圆尺寸 |
|
||||||
|
|
||||||
### 1. 基础控件
|
------
|
||||||
|
|
||||||
| 控件 | 头文件 | 描述 | 关键特性 |
|
## 🧩 控件库
|
||||||
| :---------- | :---------- | :------------ | :------------------------------------------------------ |
|
|
||||||
| **Button** | `Button.h` | 多功能按钮 | 支持多种模式/形状/状态,可设置悬停/点击颜色,自定义回调 |
|
|
||||||
| **Label** | `Label.h` | 文本标签 | 支持背景透明/不透明,自定义字体样式 |
|
|
||||||
| **TextBox** | `TextBox.h` | 输入框/显示框 | 支持输入和只读模式,集成EasyX的`InputBox` |
|
|
||||||
|
|
||||||
### 2. 容器控件
|
### 1) 基础控件
|
||||||
|
|
||||||
| 控件 | 头文件 | 描述 |
|
| 控件 | 头文件 | 描述 | 关键点 |
|
||||||
| :--------- | :--------- | :------------------------------------------------------- |
|
| ------- | ----------- | ----------- | ------------------------------------------------------------ |
|
||||||
| **Canvas** | `Canvas.h` | 容器控件,可作为其他控件的父容器,支持自定义边框和背景。 |
|
| Button | `Button.h` | 多功能按钮 | 形状/模式、悬停/点击色、回调,**单行截断** + **Tooltip**(v2.1.0) |
|
||||||
| **Window** | `Window.h` | 顶级窗口,所有控件的最终容器,负责消息循环和调度。 |
|
| Label | `Label.h` | 文本标签 | 透明/不透明背景,自定义字体 |
|
||||||
|
| TextBox | `TextBox.h` | 输入/显示框 | 输入/只读,整合 EasyX `InputBox` |
|
||||||
|
|
||||||
### 3. 高级控件
|
### 2) 容器控件
|
||||||
|
|
||||||
| 控件 | 头文件 | 描述 | 关键特性 |
|
| 控件 | 头文件 | 描述 |
|
||||||
| :-------- | :-------- | :------- | :----------------------------------------------------------- |
|
| ------ | ---------- | ------------------------------------------------------------ |
|
||||||
| **Table** | `Table.h` | 数据表格 | **框架功能亮点**,支持分页显示、自定义表头和数据、自动计算列宽、翻页按钮。 |
|
| Canvas | `Canvas.h` | 父容器,自定义边框/背景,**内置 HBox/VBox 自动布局**(v2.1.0) |
|
||||||
|
| Window | `Window.h` | 顶层容器,消息循环与分发,**可拉伸**(v2.1.0) |
|
||||||
|
|
||||||
**表格控件示例:**
|
### 3) 高级控件
|
||||||
```cpp
|
|
||||||
// 创建一个表格
|
|
||||||
auto myTable = std::make_unique<StellarX::Table>(50, 50);
|
|
||||||
|
|
||||||
// 设置表头
|
| 控件 | 头文件 | 描述 | 关键点 |
|
||||||
myTable->setHeaders({ "ID", "姓名", "年龄", "职业" });
|
| ---------- | -------------- | -------- | ------------------------------------------------------------ |
|
||||||
|
| Table | `Table.h` | 数据表格 | 分页/表头/列宽自动、翻页控件重叠/透明叠影已修复(v2.1.0) |
|
||||||
|
| Dialog | `Dialog.h` | 对话框 | 支持模态/非模态,自动布局与背景保存/恢复 |
|
||||||
|
| TabControl | `TabControl.h` | 选项卡 | 支持一键添加“页签+页”对(pair)也可以单独对某页添加子控件,采用相对坐标 |
|
||||||
|
|
||||||
// 添加数据行
|
### 4) 静态工厂
|
||||||
myTable->addDataRow({ "1", "张三", "25", "工程师" });
|
|
||||||
myTable->addDataRow({ "2", "李四", "30", "设计师" });
|
|
||||||
myTable->addDataRow({ "3", "王五", "28", "产品经理" });
|
|
||||||
|
|
||||||
// 设置每页显示2行
|
| 控件 | 头文件 | 描述 | 关键点 |
|
||||||
myTable->setRowsPerPage(2);
|
| ---------- | -------------- | ---------- | ----------------------------------- |
|
||||||
|
| MessageBox | `MessageBox.h` | 消息框工厂 | 静态 API;支持模态/非模态;内置去重 |
|
||||||
|
|
||||||
// 设置表格样式
|
------
|
||||||
myTable->textStyle.nHeight = 16;
|
|
||||||
myTable->setTableBorder(RGB(50, 50, 50));
|
|
||||||
myTable->setTableBackground(RGB(240, 240, 240));
|
|
||||||
|
|
||||||
// 添加到窗口
|
## 📐 布局管理(HBox/VBox)
|
||||||
mainWindow.addControl(std::move(myTable));
|
|
||||||
```
|
==预留,待实现==
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
## 🗂 选项卡(TabControl)
|
||||||
|
|
||||||
|
- 页签条(按钮组) + 页面容器(`Canvas`)
|
||||||
|
- 透明主题:页面区域**背景快照**切换,避免叠影
|
||||||
|
- API :**查看API文档**
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
## ✂️ 文本单行截断 & Button Tooltip
|
||||||
|
|
||||||
|
- **按钮截断**:多字节字符集下**中/英分治**,基于像素宽度阈值追加 `...`
|
||||||
|
- **Tooltip**:延时出现、自动隐藏;默认文字=按钮文本,可自定义;使用控件级**背景快照/恢复**
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
## 🧊 透明背景与背景快照
|
||||||
|
|
||||||
|
- **通用约定**:首绘前 `captureBackground(rect)`,隐藏/覆盖前 `restoreBackground()`
|
||||||
|
- **Table**:快照区域**包含表头**;翻页后立即恢复 + 重绘,分页控件整体居中
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
## 🔧 高级主题与最佳实践
|
## 🔧 高级主题与最佳实践
|
||||||
|
|
||||||
### 1. 自定义控件
|
- 自定义控件:继承 `Control`,实现 `draw()` / `handleEvent()`
|
||||||
您可以通过继承`Control`基类来创建自定义控件。只需实现`draw()`和`handleEvent()`两个纯虚函数即可。
|
- 性能:
|
||||||
|
- **脏矩形**:状态改变时置 `dirty=true`,按需重绘
|
||||||
|
- **避免额外 `cleardevice()`**:背景已由 `WM_PAINT` 统一处理
|
||||||
|
- 绘制前确保 `SetWorkingImage(nullptr)` 将输出落到屏幕
|
||||||
|
- 事件消费:处理后返回 `true` 终止传播
|
||||||
|
|
||||||
```cpp
|
------
|
||||||
class MyCustomControl : public StellarX::Control {
|
|
||||||
public:
|
|
||||||
MyCustomControl(int x, int y) : Control(x, y, 100, 100) {}
|
|
||||||
void draw() override {
|
|
||||||
// 您的自定义绘制逻辑
|
|
||||||
setfillcolor(RGB(255, 100, 100));
|
|
||||||
fillrectangle(x, y, x + width, y + height);
|
|
||||||
}
|
|
||||||
void handleEvent(const ExMessage& msg) override {
|
|
||||||
// 您的自定义事件处理逻辑
|
|
||||||
if (msg.message == WM_LBUTTONDOWN && isInControl(msg.x, msg.y)) {
|
|
||||||
// 处理点击
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 布局管理
|
## ⚠️ 适用与限制
|
||||||
当前版本星垣主要采用**绝对定位**。对于简单布局,您可以通过计算坐标来实现。对于复杂布局,可以考虑:
|
|
||||||
- 在`Canvas`中嵌套控件,实现相对定位。
|
|
||||||
- 自行实现简单的流式布局或网格布局管理器。
|
|
||||||
|
|
||||||
### 3. 性能优化
|
- 不适合高性能游戏/复杂动画;极端 DPI 需复核度量
|
||||||
- **脏矩形渲染**: 框架内部已实现,控件状态改变时`dirty=true`,仅在需要时重绘。
|
- 暂无无障碍能力
|
||||||
- **图像资源**: 使用`IMAGE`对象加载图片后,可重复使用,避免多次加载。
|
- Windows 专用,不跨平台
|
||||||
- **减少循环内操作**: 在`draw()`和`handleEvent()`中避免进行重型计算。
|
- 复杂商业前端建议用 Qt / wxWidgets / ImGui / Electron
|
||||||
|
|
||||||
## ⚠️ 重要限制与适用场景
|
------
|
||||||
|
|
||||||
**星垣框架的设计目标是轻便、清晰和教学价值,因此它明确** **不适用于** **以下场景:**
|
## 📜 许可证
|
||||||
|
|
||||||
- **高性能游戏或复杂动画**: 渲染基于EasyX的CPU软件渲染,性能有限。
|
MIT(见 `LICENSE`)。
|
||||||
- **需要高DPI缩放的应用**: 对高DPI显示器的支持有限,界面可能显示不正确。
|
|
||||||
- **需要无障碍功能的应用**: 未提供对屏幕阅读器等辅助技术的支持。
|
|
||||||
- **跨平台应用**: 深度依赖Windows API和EasyX,无法直接在Linux/macOS上运行。
|
|
||||||
- **复杂的商业软件前端**: 缺乏高级控件(如树形图、富文本框、选项卡、高级列表等)和成熟的自动布局管理器。
|
|
||||||
|
|
||||||
**如果您需要开发上述类型的应用,请考虑使用以下成熟方案:**
|
## 👥 贡献指南
|
||||||
- **Qt**: 功能极其强大,跨平台,适合大型商业应用。
|
|
||||||
- **wxWidgets**: 原生外观,跨平台。
|
|
||||||
- **Dear ImGui**: 即时模式GUI,非常适合工具和调试界面。
|
|
||||||
- **Web技术栈 (Electron/CEF)**: 适合需要Web技术的场景。
|
|
||||||
|
|
||||||
## 📜 许可证 (License)
|
- 遵循现有 C++ 风格
|
||||||
|
- 新特性需附示例与 README 更新
|
||||||
|
- 提交前请自测,并说明变更动机
|
||||||
|
- Bug/想法请提 Issue
|
||||||
|
|
||||||
本项目采用 **MIT 许可证**。
|
## 🙏 致谢
|
||||||
|
|
||||||
您可以自由地:
|
- 感谢 [EasyX](https://easyx.cn/)
|
||||||
- 使用、复制、修改、合并、出版发行、散布、再授权及销售本框架的副本。
|
- 感谢推崇**简洁/高效/清晰**的开发者
|
||||||
- 将其用于私人或商业项目。
|
|
||||||
|
|
||||||
唯一要求是:
|
------
|
||||||
- 请在您的项目中保留原始的版权声明。
|
|
||||||
|
|
||||||
详见项目根目录的 [LICENSE](LICENSE) 文件。
|
|
||||||
|
|
||||||
## 👥 贡献指南 (Contributing)
|
|
||||||
|
|
||||||
我们欢迎任何形式的贡献!如果您想为星垣框架添砖加瓦,请阅读以下指南:
|
|
||||||
|
|
||||||
1. **代码风格**: 请遵循现有的Google C++规范风格(使用空格缩进,大括号换行等)。
|
|
||||||
2. **新增功能**: 必须提供**示例代码**,并更新本README文档的相关部分。
|
|
||||||
3. **提交PR**: 请确保您的代码在提交前已经过测试,并描述清楚您的更改内容和动机。
|
|
||||||
4. **问题反馈**: 如果您发现了Bug或有新的想法,欢迎在GitHub提交Issue。
|
|
||||||
|
|
||||||
详细贡献指南请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。
|
|
||||||
|
|
||||||
## 🙏 致谢 (Acknowledgements)
|
|
||||||
|
|
||||||
- 感谢 [EasyX Graphics Library](https://easyx.cn/) 为这个项目提供了简单易用的图形基础,使得用C++教学GUI编程成为可能。
|
|
||||||
- 感谢所有追求**简洁、高效、清晰**编码理念的开发者,你们是"星垣"诞生的灵感源泉。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**星辰大海,代码为舟。**
|
**星辰大海,代码为舟。**
|
||||||
|
|
||||||
愿 `星垣 (StellarX)` 能成为您探索GUI世界的一颗可靠基石,无论是用于学习、教学还是创造实用的工具。
|
|
||||||
|
|
||||||
## 📞 支持与反馈
|
## 📞 支持与反馈
|
||||||
|
|
||||||
如果您在使用过程中遇到问题或有任何建议:
|
- 查看 [examples/](examples/)
|
||||||
1. 查看 [示例代码](examples/) 获取使用参考
|
- 查阅 [更新日志](CHANGELOG.md)[CHANGELOG](CHANGELOG.en.md)
|
||||||
2. 查阅 [更新日志](CHANGELOG.md) 了解最新变化
|
- 在 GitHub 提交 Issue
|
||||||
3. 在GitHub仓库提交Issue反馈问题
|
|
||||||
|
|
||||||
---
|
|
||||||
*星垣框架 - 轻若尘埃,繁星为界*
|
|
||||||
25
docs/CODE_OF_CONDUCT.en.md
Normal file
25
docs/CODE_OF_CONDUCT.en.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and maintainers pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment include:
|
||||||
|
|
||||||
|
* Being considerate and respectful of others
|
||||||
|
* Respecting different viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the project maintainers. All complaints will be reviewed and investigated promptly and fairly, and will result in a response that is deemed necessary and appropriate to the circumstances.
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
|
||||||
@@ -1,40 +1,56 @@
|
|||||||
/**
|
/**
|
||||||
* @file demo.cpp
|
* @file demo.cpp
|
||||||
* @brief 一个简单的演示程序,展示 StellarX GUI 框架的基本用法。
|
* @brief 一个简单的演示程序,展示 StellarX GUI 框架的基本用法。
|
||||||
* @description 创建一个带有按钮的窗口,点击按钮会改变其文本。
|
* @description 创建一个带有按钮的窗口,点击按钮会弹出对话框
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <StellarX/StellarX.h>
|
// 只需包含这一个头文件即可使用所有功能
|
||||||
#include <iostream>
|
#include "StellarX.h"
|
||||||
|
|
||||||
int main()
|
// 程序入口点(请使用WinMain以获得更好的兼容性)
|
||||||
|
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
|
||||||
{
|
{
|
||||||
// 创建一个窗口 (Windows平台)
|
// 1. 创建一个640x480的窗口,背景为白色,标题为"我的应用"
|
||||||
Window mainWindow(800, 600, NULL, RGB(240, 240, 240), "StellarX 演示程序");
|
Window mainWindow(640, 480, 0, RGB(255, 255, 255), "我的第一个星垣应用");
|
||||||
|
|
||||||
// 创建一个按钮
|
// 2. 创建一个按钮 (使用智能指针管理)
|
||||||
auto myButton = std::make_unique<Button>(300, 250, 200, 80, "点击我!", StellarX::ButtonMode::NORMAL, StellarX::ControlShape::ROUND_RECTANGLE);
|
auto myButton = std::make_unique<Button>(
|
||||||
|
250, 200, 140, 40, // x, y, 宽度, 高度
|
||||||
|
"点击我", // 按钮文本
|
||||||
|
StellarX::ButtonMode::NORMAL,
|
||||||
|
StellarX::ControlShape::ROUND_RECTANGLE
|
||||||
|
);
|
||||||
|
|
||||||
// 为按钮点击事件设置一个回调函数
|
// 3. 为按钮设置点击事件(使用Lambda表达式)
|
||||||
myButton->setOnClickListener([&myButton]() {
|
myButton->setOnClickListener([&mainWindow]() {
|
||||||
std::cout << "按钮被点击了!" << std::endl;
|
// 使用消息框工厂创建模态对话框
|
||||||
// 点击后改变按钮文本作为视觉反馈
|
auto result = StellarX::MessageBox::ShowModal(
|
||||||
static bool 已切换 = false;
|
mainWindow,
|
||||||
if (已切换) {
|
"欢迎使用星垣GUI\r\n作者:我在人间做废物",
|
||||||
myButton->setButtonText("点击我!");
|
"问候",
|
||||||
}
|
StellarX::MessageBoxType::OKCancel
|
||||||
else {
|
);
|
||||||
myButton->setButtonText("被点过了!");
|
// 处理对话框结果
|
||||||
}
|
if (result == StellarX::MessageBoxResult::OK) {
|
||||||
已切换 = !已切换;
|
// 用户点击了确定按钮
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 将按钮添加到窗口
|
|
||||||
mainWindow.addControl(std::move(myButton));
|
|
||||||
|
|
||||||
// 绘制窗口并运行事件循环 (Windows消息循环)
|
|
||||||
mainWindow.draw();
|
|
||||||
mainWindow.runEventLoop();
|
|
||||||
|
|
||||||
return 0;
|
// 4. (可选)设置按钮样式
|
||||||
|
myButton->textStyle.nHeight = 20;
|
||||||
|
myButton->textStyle.color = RGB(0, 0, 128); // 深蓝色文字
|
||||||
|
myButton->setButtonBorder(RGB(0, 128, 255)); // 蓝色边框
|
||||||
|
|
||||||
|
// 5. 将按钮添加到窗口
|
||||||
|
mainWindow.addControl(std::move(myButton));
|
||||||
|
|
||||||
|
// 6. 绘制窗口
|
||||||
|
mainWindow.draw();
|
||||||
|
|
||||||
|
// 7. 进入消息循环,等待用户交互
|
||||||
|
mainWindow.runEventLoop();
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
17
examples/register-viewer/README.md
Normal file
17
examples/register-viewer/README.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Register Viewer (StellarX example)
|
||||||
|
|
||||||
|
**A 32-bit register visualizer built with StellarX** (Windows + EasyX).
|
||||||
|
Features:
|
||||||
|
- 32 toggle bits (MSB→LSB = 31..0)
|
||||||
|
- Range invert, logical left/right shift
|
||||||
|
- Hex/Decimal display with signed/unsigned toggle
|
||||||
|
- Grouped binary view with "last/current" snapshots
|
||||||
|
- One-click set all 0 / all 1
|
||||||
|
|
||||||
|
## Build & Run
|
||||||
|
1. Ensure StellarX and EasyX are in your include/lib paths.
|
||||||
|
2. Build as a normal C++ Win32 desktop app (e.g., VS 2019+).
|
||||||
|
3. Run the executable.
|
||||||
|
|
||||||
|
This example demonstrates StellarX APIs with a single `main.cpp` (~450 LOC):
|
||||||
|
`Window / Canvas / Button / Label / TextBox` + simple event callbacks.
|
||||||
469
examples/register-viewer/main.cpp
Normal file
469
examples/register-viewer/main.cpp
Normal file
@@ -0,0 +1,469 @@
|
|||||||
|
// 本工具基于 StellarX 构建,轻量级的 Windows GUI 框架。
|
||||||
|
#include"StellarX.h"
|
||||||
|
#include <sstream>
|
||||||
|
#include<iomanip>
|
||||||
|
#include<array>
|
||||||
|
|
||||||
|
auto blackColor = RGB(202, 255, 255);
|
||||||
|
char initData[33] = "00000000000000000000000000000000";//初始数据
|
||||||
|
bool gSigned = false; //是否为有符号数
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
Window mainWindow(700, 510, NULL, RGB(255, 255, 255), "寄存器查看工具 V1.0——我在人间做废物 (同类工具定制:3150131407(Q / V))");
|
||||||
|
|
||||||
|
//选择区控件
|
||||||
|
auto selectionAreaLabel = std::make_unique<Label>(18, 0, "32位选择区");
|
||||||
|
selectionAreaLabel->setTextdisap(true);
|
||||||
|
std::vector<std::unique_ptr<Label>>selectionAreaButtonLabel;
|
||||||
|
std::vector<std::unique_ptr<Button>>selectionAreaButton;
|
||||||
|
std::vector<Button*>selectionAreaButton_ptr;
|
||||||
|
auto selectionArea = std::make_unique <Canvas>(10, 10, 680, 150);
|
||||||
|
|
||||||
|
selectionArea->setCanvasBkColor(blackColor);
|
||||||
|
selectionArea->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
|
||||||
|
|
||||||
|
for (int y = 0; y < 2; y++)
|
||||||
|
{
|
||||||
|
std::ostringstream os;
|
||||||
|
for (int x = 0; x < 16; x++)
|
||||||
|
{
|
||||||
|
if (0 == y)
|
||||||
|
{
|
||||||
|
selectionAreaButtonLabel.push_back(std::make_unique<Label>(x * 35 + 25 + 28 * (x / 4), 26, "", RGB(208, 208, 208)));
|
||||||
|
os << std::setw(2) << std::setfill('0') << 31 - x;
|
||||||
|
selectionAreaButtonLabel.back()->setText(os.str());
|
||||||
|
selectionAreaButtonLabel.back()->setTextdisap(true);
|
||||||
|
|
||||||
|
selectionAreaButton.push_back(
|
||||||
|
std::make_unique<Button>(x * 35 + 27 + 28 * (x / 4), 58, 25, 30, "0",
|
||||||
|
blackColor, RGB(171, 196, 220), StellarX::ButtonMode::TOGGLE));
|
||||||
|
selectionAreaButton.back()->textStyle.color = RGB(226, 116, 152);
|
||||||
|
selectionAreaButton.back()->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
|
||||||
|
selectionAreaButton_ptr.push_back(selectionAreaButton.back().get());
|
||||||
|
int k = 32 - x - 1;
|
||||||
|
//选择区按钮被点击后在二进制0和1之间切换,并更新initData
|
||||||
|
selectionAreaButton_ptr.back()->setOnToggleOnListener([k, btn = selectionAreaButton_ptr.back()]()
|
||||||
|
{
|
||||||
|
btn->setButtonText("1");
|
||||||
|
initData[k] = '1';
|
||||||
|
});
|
||||||
|
selectionAreaButton_ptr.back()->setOnToggleOffListener([k, btn = selectionAreaButton_ptr.back()]()
|
||||||
|
{
|
||||||
|
btn->setButtonText("0");
|
||||||
|
initData[k] = '0';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
selectionAreaButtonLabel.push_back(std::make_unique<Label>(x * 35 + 25 + 28 * (x / 4), 90, "", RGB(208, 208, 208)));
|
||||||
|
os << std::setw(2) << std::setfill('0') << 15 - x;
|
||||||
|
selectionAreaButtonLabel.back()->setText(os.str());
|
||||||
|
selectionAreaButtonLabel.back()->setTextdisap(true);
|
||||||
|
|
||||||
|
selectionAreaButton.push_back(
|
||||||
|
std::make_unique<Button>(x * 35 + 27 + 28 * (x / 4), 120, 25, 30, "0",
|
||||||
|
blackColor, RGB(171, 196, 220), StellarX::ButtonMode::TOGGLE));
|
||||||
|
selectionAreaButton.back()->textStyle.color = RGB(226, 116, 152);
|
||||||
|
selectionAreaButton.back()->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
|
||||||
|
selectionAreaButton_ptr.push_back(selectionAreaButton.back().get());
|
||||||
|
int k = 15 - x;
|
||||||
|
selectionAreaButton.back()->setOnToggleOnListener([k, btn = selectionAreaButton_ptr.back()]()
|
||||||
|
{
|
||||||
|
btn->setButtonText("1");
|
||||||
|
initData[k] = '1';
|
||||||
|
});
|
||||||
|
selectionAreaButton.back()->setOnToggleOffListener([k, btn = selectionAreaButton_ptr.back()]()
|
||||||
|
{
|
||||||
|
btn->setButtonText("0");
|
||||||
|
initData[k] = '0';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
os.str("");
|
||||||
|
os.clear();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectionArea->addControl(std::move(selectionAreaLabel));
|
||||||
|
for (auto& s : selectionAreaButton)
|
||||||
|
selectionArea->addControl(std::move(s));
|
||||||
|
for (auto& s : selectionAreaButtonLabel)
|
||||||
|
selectionArea->addControl(std::move(s));
|
||||||
|
//功能区控件
|
||||||
|
//功能区总容器
|
||||||
|
auto function = std::make_unique<Canvas>(10, 170, 680, 70);
|
||||||
|
function->setCanvasfillMode(StellarX::FillMode::Null);
|
||||||
|
function->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
|
||||||
|
function->setCanvasBkColor(blackColor);
|
||||||
|
auto bitInvert_que = std::make_unique<Canvas>(0, 0, 220, 70);
|
||||||
|
auto leftShift_que = std::make_unique<Canvas>(230, 0, 220, 70);
|
||||||
|
auto rightShift_que = std::make_unique<Canvas>(460, 0, 220, 70);
|
||||||
|
|
||||||
|
auto bitInvert = bitInvert_que.get();
|
||||||
|
auto leftShift = leftShift_que.get();
|
||||||
|
auto rightShift = rightShift_que.get();
|
||||||
|
|
||||||
|
bitInvert->setCanvasBkColor(blackColor);
|
||||||
|
bitInvert->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
|
||||||
|
leftShift->setCanvasBkColor(blackColor);
|
||||||
|
leftShift->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
|
||||||
|
rightShift->setCanvasBkColor(blackColor);
|
||||||
|
rightShift->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
|
||||||
|
|
||||||
|
function->addControl(std::move(bitInvert_que));
|
||||||
|
function->addControl(std::move(leftShift_que));
|
||||||
|
function->addControl(std::move(rightShift_que));
|
||||||
|
|
||||||
|
auto bitInvertLabel = std::make_unique<Label>(13, -10, "位取反");
|
||||||
|
bitInvertLabel->setTextdisap(true);
|
||||||
|
auto leftShiftLabel = std::make_unique<Label>(13, -10, "左移位");
|
||||||
|
leftShiftLabel->setTextdisap(true);
|
||||||
|
auto rightShiftLabel = std::make_unique<Label>(13, -10, "右移位");
|
||||||
|
rightShiftLabel->setTextdisap(true);
|
||||||
|
|
||||||
|
// ====== 公用小工具======
|
||||||
|
auto clamp = [](int v, int lo, int hi) { return v < lo ? lo : (v > hi ? hi : v); };
|
||||||
|
auto toInt = [](const std::string& s, int def = 0) {
|
||||||
|
try { return std::stoi(s); }
|
||||||
|
catch (...) { return def; }
|
||||||
|
};
|
||||||
|
// bit号(31..0) -> selectionAreaButton下标(0..31)
|
||||||
|
auto vecIndexFromBit = [](int bit) { return 31 - bit; };
|
||||||
|
|
||||||
|
// 读取当前32位点击态
|
||||||
|
auto snapshotBits = [&]() {
|
||||||
|
std::array<bool, 32> a{};
|
||||||
|
for (int b = 0; b < 32; ++b)
|
||||||
|
a[b] = selectionAreaButton_ptr[vecIndexFromBit(b)]->isClicked();
|
||||||
|
return a;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 应用目标态:仅当不同才 setButtonClick
|
||||||
|
auto applyBits = [&](const std::array<bool, 32>& a) {
|
||||||
|
for (int b = 0; b < 32; ++b) {
|
||||||
|
auto btn = selectionAreaButton_ptr[vecIndexFromBit(b)];
|
||||||
|
if (btn->isClicked() != a[b]) btn->setButtonClick(a[b]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//取反区控件
|
||||||
|
std::array<std::unique_ptr<Label>, 4> bitInvertFunctionLabel;
|
||||||
|
bitInvertFunctionLabel[0] = std::make_unique<Label>(30, 10, "低位");
|
||||||
|
bitInvertFunctionLabel[1] = std::make_unique<Label>(90, 10, "高位");
|
||||||
|
bitInvertFunctionLabel[2] = std::make_unique<Label>(15, 38, "从");
|
||||||
|
bitInvertFunctionLabel[3] = std::make_unique<Label>(75, 38, "到");
|
||||||
|
|
||||||
|
std::array<std::unique_ptr<TextBox>, 2> bitInvertFunctionTextBox;
|
||||||
|
bitInvertFunctionTextBox[0] = std::make_unique<TextBox>(35, 35, 35, 30, "0");
|
||||||
|
bitInvertFunctionTextBox[1] = std::make_unique<TextBox>(95, 35, 35, 30, "0");
|
||||||
|
auto invL = bitInvertFunctionTextBox[0].get();
|
||||||
|
auto invH = bitInvertFunctionTextBox[1].get();
|
||||||
|
auto bitInvertFunctionButton = std::make_unique<Button>(135, 35, 80, 30, "位取反",
|
||||||
|
blackColor, RGB(171, 196, 220));
|
||||||
|
bitInvertFunctionButton->textStyle.color = RGB(226, 116, 152);
|
||||||
|
bitInvertFunctionButton->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
|
||||||
|
auto bitInvertFunctionButton_ptr = bitInvertFunctionButton.get();
|
||||||
|
|
||||||
|
bitInvert->addControl(std::move(bitInvertFunctionButton));
|
||||||
|
bitInvert->addControl(std::move(bitInvertLabel));
|
||||||
|
for (auto& b : bitInvertFunctionTextBox)
|
||||||
|
{
|
||||||
|
b->setMaxCharLen(3);
|
||||||
|
b->textStyle.color = RGB(226, 116, 152);
|
||||||
|
b->setTextBoxBk(RGB(244, 234, 142));
|
||||||
|
b->setTextBoxshape(StellarX::ControlShape::B_RECTANGLE);
|
||||||
|
bitInvert->addControl(std::move(b));
|
||||||
|
}
|
||||||
|
for (auto& b : bitInvertFunctionLabel)
|
||||||
|
{
|
||||||
|
b->setTextdisap(true);
|
||||||
|
bitInvert->addControl(std::move(b));
|
||||||
|
}
|
||||||
|
//左移控件
|
||||||
|
auto leftShiftFunctionLabel = std::make_unique<Label>(198, 30, "位");
|
||||||
|
leftShiftFunctionLabel->setTextdisap(true);
|
||||||
|
|
||||||
|
auto leftShiftFunctionTextBox = std::make_unique<TextBox>(90, 30, 100, 30, "0");
|
||||||
|
leftShiftFunctionTextBox->setMaxCharLen(3);
|
||||||
|
leftShiftFunctionTextBox->textStyle.color = RGB(226, 116, 152);
|
||||||
|
leftShiftFunctionTextBox->setTextBoxBk(RGB(244, 234, 142));
|
||||||
|
leftShiftFunctionTextBox->setTextBoxshape(StellarX::ControlShape::B_RECTANGLE);
|
||||||
|
auto shlBox = leftShiftFunctionTextBox.get();
|
||||||
|
auto leftShiftFunctionButton = std::make_unique<Button>(15, 30, 60, 30, "左移",
|
||||||
|
blackColor, RGB(171, 196, 220));
|
||||||
|
leftShiftFunctionButton->textStyle.color = RGB(226, 116, 152);
|
||||||
|
leftShiftFunctionButton->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
|
||||||
|
auto leftShiftFunctionButton_ptr = leftShiftFunctionButton.get();
|
||||||
|
|
||||||
|
|
||||||
|
leftShift->addControl(std::move(leftShiftFunctionButton));
|
||||||
|
leftShift->addControl(std::move(leftShiftFunctionTextBox));
|
||||||
|
|
||||||
|
leftShift->addControl(std::move(leftShiftLabel));
|
||||||
|
leftShift->addControl(std::move(leftShiftFunctionLabel));
|
||||||
|
|
||||||
|
//右移控件
|
||||||
|
auto rightShiftFunctionLabel = std::make_unique<Label>(198, 30, "位");
|
||||||
|
rightShiftFunctionLabel->setTextdisap(true);
|
||||||
|
auto rightShiftFunctionTextBox = std::make_unique<TextBox>(90, 30, 100, 30, "0");
|
||||||
|
rightShiftFunctionTextBox->setMaxCharLen(3);
|
||||||
|
rightShiftFunctionTextBox->textStyle.color = RGB(226, 116, 152);
|
||||||
|
rightShiftFunctionTextBox->setTextBoxBk(RGB(244, 234, 142));
|
||||||
|
rightShiftFunctionTextBox->setTextBoxshape(StellarX::ControlShape::B_RECTANGLE);
|
||||||
|
auto shrBox = rightShiftFunctionTextBox.get();
|
||||||
|
|
||||||
|
auto rightShiftFunctionButton = std::make_unique<Button>(15, 30, 60, 30, "右移",
|
||||||
|
blackColor, RGB(171, 196, 220));
|
||||||
|
rightShiftFunctionButton->textStyle.color = RGB(226, 116, 152);
|
||||||
|
rightShiftFunctionButton->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
|
||||||
|
auto rightShiftFunctionButton_ptr = rightShiftFunctionButton.get();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
rightShift->addControl(std::move(rightShiftFunctionButton));
|
||||||
|
|
||||||
|
rightShift->addControl(std::move(rightShiftFunctionTextBox));
|
||||||
|
rightShift->addControl(std::move(rightShiftLabel));
|
||||||
|
rightShift->addControl(std::move(rightShiftFunctionLabel));
|
||||||
|
|
||||||
|
|
||||||
|
//显示区控件
|
||||||
|
//数值显示
|
||||||
|
auto NumericalDisplayArea = std::make_unique<Canvas>(10, 255, 680, 70);
|
||||||
|
NumericalDisplayArea->setCanvasBkColor(blackColor);
|
||||||
|
NumericalDisplayArea->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
|
||||||
|
|
||||||
|
std::array<std::unique_ptr<Label>, 3> NumericalDisplayAreaLabel;
|
||||||
|
NumericalDisplayAreaLabel[0] = std::make_unique<Label>(18, -10, "数值显示区");
|
||||||
|
NumericalDisplayAreaLabel[1] = std::make_unique<Label>(20, 25, "十六进制");
|
||||||
|
NumericalDisplayAreaLabel[2] = std::make_unique<Label>(330, 25, "十进制");
|
||||||
|
|
||||||
|
std::array<std::unique_ptr<TextBox>, 2> NumericalDisplayAreaTextBox;
|
||||||
|
NumericalDisplayAreaTextBox[0] = std::make_unique<TextBox>(110, 25, 200, 30, "0");
|
||||||
|
NumericalDisplayAreaTextBox[1] = std::make_unique<TextBox>(400, 25, 200, 30, "0");
|
||||||
|
auto hex = NumericalDisplayAreaTextBox[0].get();
|
||||||
|
auto dec = NumericalDisplayAreaTextBox[1].get();
|
||||||
|
|
||||||
|
|
||||||
|
for (auto& b : NumericalDisplayAreaLabel)
|
||||||
|
{
|
||||||
|
b->setTextdisap(true);
|
||||||
|
NumericalDisplayArea->addControl(std::move(b));
|
||||||
|
}
|
||||||
|
for (auto& b : NumericalDisplayAreaTextBox)
|
||||||
|
{
|
||||||
|
b->setMaxCharLen(11);
|
||||||
|
b->textStyle.color = RGB(255, 69, 0);
|
||||||
|
b->setTextBoxBk(RGB(141, 141, 141));
|
||||||
|
b->setTextBoxshape(StellarX::ControlShape::B_RECTANGLE);
|
||||||
|
b->setMode(StellarX::TextBoxmode::READONLY_MODE);
|
||||||
|
NumericalDisplayArea->addControl(std::move(b));
|
||||||
|
}
|
||||||
|
//二进制显示
|
||||||
|
auto BinaryDisplayArea = std::make_unique<Canvas>(10, 335, 680, 110);
|
||||||
|
BinaryDisplayArea->setCanvasBkColor(blackColor);
|
||||||
|
BinaryDisplayArea->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
|
||||||
|
|
||||||
|
std::array<std::unique_ptr<Label>, 3> BinaryDisplayAreaLabel;
|
||||||
|
BinaryDisplayAreaLabel[0] = std::make_unique<Label>(18, -10, "二进制显示区");
|
||||||
|
BinaryDisplayAreaLabel[1] = std::make_unique<Label>(35, 20, "上次值");
|
||||||
|
BinaryDisplayAreaLabel[2] = std::make_unique<Label>(35, 67, "本次值");
|
||||||
|
|
||||||
|
std::array<std::unique_ptr<TextBox>, 2> BinaryDisplayAreaTextBox;
|
||||||
|
BinaryDisplayAreaTextBox[0] = std::make_unique<TextBox>(110, 20, 520, 30, "0000_0000_0000_0000_0000_0000_0000_0000");
|
||||||
|
BinaryDisplayAreaTextBox[1] = std::make_unique<TextBox>(110, 67, 520, 30, "0000_0000_0000_0000_0000_0000_0000_0000");
|
||||||
|
auto Last = BinaryDisplayAreaTextBox[0].get();
|
||||||
|
auto This = BinaryDisplayAreaTextBox[1].get();
|
||||||
|
|
||||||
|
|
||||||
|
for (auto& b : BinaryDisplayAreaLabel)
|
||||||
|
{
|
||||||
|
b->setTextdisap(true);
|
||||||
|
BinaryDisplayArea->addControl(std::move(b));
|
||||||
|
}
|
||||||
|
for (auto& b : BinaryDisplayAreaTextBox)
|
||||||
|
{
|
||||||
|
b->setMaxCharLen(40);
|
||||||
|
b->textStyle.color = RGB(255, 69, 0);
|
||||||
|
b->setTextBoxBk(RGB(141, 141, 141));
|
||||||
|
b->setTextBoxshape(StellarX::ControlShape::B_RECTANGLE);
|
||||||
|
b->setMode(StellarX::TextBoxmode::READONLY_MODE);
|
||||||
|
BinaryDisplayArea->addControl(std::move(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 由位图 bits 生成数值
|
||||||
|
auto valueFromBits = [](const std::array<bool, 32>& bits) -> uint32_t {
|
||||||
|
uint32_t v = 0;
|
||||||
|
for (int b = 0; b < 32; ++b) if (bits[b]) v |= (1u << b);
|
||||||
|
return v;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 由位图 bits 生成 "0000_0000_..._0000"(MSB→LSB:31..0)
|
||||||
|
auto binaryGroupedFromBits = [](const std::array<bool, 32>& bits) -> std::string {
|
||||||
|
std::string s; s.reserve(39);
|
||||||
|
for (int b = 31; b >= 0; --b) {
|
||||||
|
s.push_back(bits[b] ? '1' : '0');
|
||||||
|
if (b % 4 == 0 && b != 0) s.push_back('_');
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 用“目标位图 bits”刷新显示区
|
||||||
|
auto refreshDisplaysWithBits = [&](const std::string& prevThis,
|
||||||
|
const std::array<bool, 32>& bits,
|
||||||
|
TextBox* hex, TextBox* dec, TextBox* Last, TextBox* This)
|
||||||
|
{
|
||||||
|
const uint32_t val = valueFromBits(bits);
|
||||||
|
const int32_t s = static_cast<int32_t>(val);
|
||||||
|
char hexbuf[16];
|
||||||
|
std::snprintf(hexbuf, sizeof(hexbuf), "%08X", val);
|
||||||
|
|
||||||
|
hex->setText(hexbuf); // HEX(大写8位)
|
||||||
|
dec->setText(gSigned ? std::to_string(s) : std::to_string(val)); // DEC
|
||||||
|
Last->setText(prevThis); // 上次值 ← 刷新前的本次值
|
||||||
|
This->setText(binaryGroupedFromBits(bits)); // 本次值 ← 由“目标位图”生成
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
bitInvertFunctionButton_ptr->setOnClickListener([=, &snapshotBits, &applyBits, &refreshDisplaysWithBits]() {
|
||||||
|
const std::string prevThis = This->getText();
|
||||||
|
|
||||||
|
int L = clamp(toInt(invL->getText(), 0), 0, 31);
|
||||||
|
int H = clamp(toInt(invH->getText(), 0), 0, 31);
|
||||||
|
if (L > H) std::swap(L, H);
|
||||||
|
|
||||||
|
auto cur = snapshotBits();
|
||||||
|
for (int b = L; b <= H; ++b) cur[b] = !cur[b];
|
||||||
|
|
||||||
|
applyBits(cur); // 只改按钮点击态(触发位按钮自回调)
|
||||||
|
refreshDisplaysWithBits(prevThis, cur, hex, dec, Last, This);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
leftShiftFunctionButton_ptr->setOnClickListener([=, &snapshotBits, &applyBits, &refreshDisplaysWithBits]() {
|
||||||
|
const std::string prevThis = This->getText();
|
||||||
|
|
||||||
|
int n = clamp(toInt(shlBox->getText(), 0), 0, 31);
|
||||||
|
auto cur = snapshotBits();
|
||||||
|
std::array<bool, 32> nxt{}; // 默认全 0
|
||||||
|
|
||||||
|
// 逻辑左移:高位丢弃、低位补 0
|
||||||
|
for (int b = 31; b >= 0; --b) nxt[b] = (b >= n) ? cur[b - n] : false;
|
||||||
|
|
||||||
|
applyBits(nxt);
|
||||||
|
refreshDisplaysWithBits(prevThis, nxt, hex, dec, Last, This);
|
||||||
|
});
|
||||||
|
|
||||||
|
rightShiftFunctionButton_ptr->setOnClickListener([=, &snapshotBits, &applyBits, &refreshDisplaysWithBits]() {
|
||||||
|
const std::string prevThis = This->getText();
|
||||||
|
|
||||||
|
int n = clamp(toInt(shrBox->getText(), 0), 0, 31);
|
||||||
|
auto cur = snapshotBits();
|
||||||
|
std::array<bool, 32> nxt{};
|
||||||
|
|
||||||
|
// 逻辑右移:低位丢弃、高位补 0
|
||||||
|
for (int b = 0; b < 32; ++b) nxt[b] = (b + n <= 31) ? cur[b + n] : false;
|
||||||
|
|
||||||
|
applyBits(nxt);
|
||||||
|
refreshDisplaysWithBits(prevThis, nxt, hex, dec, Last, This);
|
||||||
|
});
|
||||||
|
|
||||||
|
//配置区控件clearrectangle(10, 440, 690, 490);
|
||||||
|
auto configuration = std::make_unique<Canvas>(10, 455, 680, 40);
|
||||||
|
configuration->setCanvasBkColor(blackColor);
|
||||||
|
configuration->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
|
||||||
|
|
||||||
|
auto configurationLabel = std::make_unique<Label>(20, -10, "配置区");
|
||||||
|
configurationLabel->setTextdisap(true);
|
||||||
|
|
||||||
|
std::array<std::unique_ptr<Button>, 2> configurationButton;
|
||||||
|
configurationButton[0] = std::make_unique<Button>(420, 10, 90, 20, "一键置0",
|
||||||
|
blackColor, RGB(171, 196, 220));
|
||||||
|
configurationButton[0]->textStyle.color = RGB(226, 116, 152);
|
||||||
|
configurationButton[0]->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
|
||||||
|
|
||||||
|
configurationButton[1] = std::make_unique<Button>(530, 10, 90, 20, "一键置1",
|
||||||
|
blackColor, RGB(171, 196, 220));
|
||||||
|
configurationButton[1]->textStyle.color = RGB(226, 116, 152);
|
||||||
|
configurationButton[1]->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
|
||||||
|
configurationButton[0]->setOnClickListener(
|
||||||
|
[&]() {
|
||||||
|
for (auto& s : selectionAreaButton_ptr)
|
||||||
|
if (s->isClicked()) s->setButtonClick(false);
|
||||||
|
|
||||||
|
// 刷新显示:prevThis 用当前 This 文本
|
||||||
|
const std::string prevThis = This->getText();
|
||||||
|
auto cur = snapshotBits();
|
||||||
|
{
|
||||||
|
char hexbuf[16];
|
||||||
|
uint32_t u = 0; for (int b = 0; b < 32; ++b) if (cur[b]) u |= (1u << b);
|
||||||
|
int32_t s = static_cast<int32_t>(u);
|
||||||
|
std::snprintf(hexbuf, sizeof(hexbuf), "%08X", u);
|
||||||
|
hex->setText(hexbuf);
|
||||||
|
dec->setText(gSigned ? std::to_string(s) : std::to_string(u));
|
||||||
|
Last->setText(prevThis);
|
||||||
|
This->setText(binaryGroupedFromBits(cur));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
configurationButton[1]->setOnClickListener(
|
||||||
|
[&]() {
|
||||||
|
for (auto& s : selectionAreaButton_ptr)
|
||||||
|
if (!s->isClicked()) s->setButtonClick(true);
|
||||||
|
|
||||||
|
const std::string prevThis = This->getText();
|
||||||
|
auto cur = snapshotBits();
|
||||||
|
char hexbuf[16];
|
||||||
|
uint32_t u = 0; for (int b = 0; b < 32; ++b) if (cur[b]) u |= (1u << b);
|
||||||
|
int32_t s = static_cast<int32_t>(u);
|
||||||
|
std::snprintf(hexbuf, sizeof(hexbuf), "%08X", u);
|
||||||
|
hex->setText(hexbuf);
|
||||||
|
dec->setText(gSigned ? std::to_string(s) : std::to_string(u));
|
||||||
|
Last->setText(prevThis);
|
||||||
|
This->setText(binaryGroupedFromBits(cur));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
auto signedToggle = std::make_unique<Button>(
|
||||||
|
330, 10, 80, 20, "无符号",
|
||||||
|
blackColor, RGB(171, 196, 220), StellarX::ButtonMode::TOGGLE);
|
||||||
|
signedToggle->textStyle.color = RGB(226, 116, 152);
|
||||||
|
signedToggle->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
|
||||||
|
auto* signedTogglePtr = signedToggle.get();
|
||||||
|
|
||||||
|
signedTogglePtr->setOnToggleOnListener([&]() {
|
||||||
|
gSigned = true;
|
||||||
|
signedTogglePtr->setButtonText("有符号");
|
||||||
|
StellarX::MessageBox::showModal(mainWindow, "有符号模式下,\n最高位为符号位,\n其余位为数值位。", "有符号模式");
|
||||||
|
// 立即刷新十进制显示:用当前位图算出新值,仅改 dec
|
||||||
|
auto cur = snapshotBits();
|
||||||
|
const uint32_t u = [&] { uint32_t v = 0; for (int b = 0; b < 32; ++b) if (cur[b]) v |= (1u << b); return v; }();
|
||||||
|
const int32_t s = static_cast<int32_t>(u);
|
||||||
|
dec->setText(std::to_string(s));
|
||||||
|
});
|
||||||
|
signedTogglePtr->setOnToggleOffListener([&]() {
|
||||||
|
gSigned = false;
|
||||||
|
signedTogglePtr->setButtonText("无符号");
|
||||||
|
StellarX::MessageBox::showAsync(mainWindow, "无符号模式下,\n所有位均为数值位。", "无符号模式");
|
||||||
|
auto cur = snapshotBits();
|
||||||
|
const uint32_t u = [&] { uint32_t v = 0; for (int b = 0; b < 32; ++b) if (cur[b]) v |= (1u << b); return v; }();
|
||||||
|
dec->setText(std::to_string(u));
|
||||||
|
});
|
||||||
|
|
||||||
|
signedTogglePtr->enableTooltip(true);
|
||||||
|
signedTogglePtr->setTooltipTextsForToggle("切换无符号模式", "切换有符号模式");
|
||||||
|
|
||||||
|
configuration->addControl(std::move(configurationButton[0]));
|
||||||
|
configuration->addControl(std::move(configurationButton[1]));
|
||||||
|
configuration->addControl(std::move(signedToggle));
|
||||||
|
configuration->addControl(std::move(configurationLabel));
|
||||||
|
|
||||||
|
mainWindow.addControl(std::move(selectionArea));
|
||||||
|
mainWindow.addControl(std::move(function));
|
||||||
|
mainWindow.addControl(std::move(NumericalDisplayArea));
|
||||||
|
mainWindow.addControl(std::move(BinaryDisplayArea));
|
||||||
|
mainWindow.addControl(std::move(configuration));
|
||||||
|
|
||||||
|
mainWindow.draw();
|
||||||
|
return mainWindow.runEventLoop();
|
||||||
|
}
|
||||||
BIN
image/1.png
Normal file
BIN
image/1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
BIN
image/2.png
Normal file
BIN
image/2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
@@ -1,16 +1,30 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* @文件: Button.h
|
* @类: Button
|
||||||
* @摘要: 按钮控件类
|
* @摘要: 多功能按钮控件,支持多种状态和样式
|
||||||
* @描述:
|
* @描述:
|
||||||
* 提供多种样式和行为的按钮控件,支持点击、悬停、切换等状态。
|
* 提供完整的按钮功能,包括普通点击、切换模式、禁用状态。
|
||||||
* 继承自Control基类,是框架的核心交互组件之一。
|
* 支持多种形状(矩形、圆形、椭圆等)和丰富的视觉样式。
|
||||||
*
|
* 通过回调函数机制实现灵活的交互逻辑。
|
||||||
* @所属框架: 星垣(StellarX) GUI框架
|
*
|
||||||
* @作者: 我在人间做废物
|
* @特性:
|
||||||
* @日期: September 2025
|
* - 支持三种工作模式:普通、切换、禁用
|
||||||
******************************************************************************/
|
* - 八种几何形状,各有边框和无边框版本
|
||||||
|
* - 自定义颜色(默认、悬停、点击状态)
|
||||||
|
* - 多种填充模式(纯色、图案、图像)
|
||||||
|
* - 完整的鼠标事件处理(点击、悬停、移出)
|
||||||
|
*
|
||||||
|
* @使用场景: 作为主要交互控件,用于触发动作或表示状态
|
||||||
|
* @所属框架: 星垣(StellarX) GUI框架
|
||||||
|
* @作者: 我在人间做废物
|
||||||
|
******************************************************************************/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "Control.h"
|
#include "Control.h"
|
||||||
|
#include"label.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define DISABLEDCOLOUR RGB(96, 96, 96) //禁用状态颜色
|
||||||
|
#define TEXTMARGINS_X 6
|
||||||
|
#define TEXTMARGINS_Y 4
|
||||||
|
|
||||||
class Button : public Control
|
class Button : public Control
|
||||||
{
|
{
|
||||||
@@ -18,7 +32,12 @@ class Button : public Control
|
|||||||
std::string text; // 按钮上的文字
|
std::string text; // 按钮上的文字
|
||||||
bool click; // 是否被点击
|
bool click; // 是否被点击
|
||||||
bool hover; // 是否被悬停
|
bool hover; // 是否被悬停
|
||||||
bool dirty = true; // 是否重绘
|
|
||||||
|
std::string cutText; // 切割后的文本
|
||||||
|
bool needCutText = true; // 是否需要切割文本
|
||||||
|
bool isUseCutText = false; // 是否使用切割文本
|
||||||
|
int padX = TEXTMARGINS_X; // 文本最小左右内边距
|
||||||
|
int padY = TEXTMARGINS_Y; // 文本最小上下内边距
|
||||||
|
|
||||||
COLORREF buttonTrueColor; // 按钮被点击后的颜色
|
COLORREF buttonTrueColor; // 按钮被点击后的颜色
|
||||||
COLORREF buttonFalseColor; // 按钮未被点击的颜色
|
COLORREF buttonFalseColor; // 按钮未被点击的颜色
|
||||||
@@ -28,7 +47,7 @@ class Button : public Control
|
|||||||
StellarX::ButtonMode mode; // 按钮模式
|
StellarX::ButtonMode mode; // 按钮模式
|
||||||
StellarX::ControlShape shape; // 按钮形状
|
StellarX::ControlShape shape; // 按钮形状
|
||||||
|
|
||||||
int buttonFillMode = BS_SOLID; //按钮填充模式
|
StellarX::FillMode buttonFillMode = StellarX::FillMode::Solid; //按钮填充模式
|
||||||
StellarX::FillStyle buttonFillIma = StellarX::FillStyle::BDiagonal; //按钮填充图案
|
StellarX::FillStyle buttonFillIma = StellarX::FillStyle::BDiagonal; //按钮填充图案
|
||||||
IMAGE* buttonFileIMAGE = nullptr; //按钮填充图像
|
IMAGE* buttonFileIMAGE = nullptr; //按钮填充图像
|
||||||
|
|
||||||
@@ -43,16 +62,31 @@ class Button : public Control
|
|||||||
int oldtext_height = -1;
|
int oldtext_height = -1;
|
||||||
int text_width = 0;
|
int text_width = 0;
|
||||||
int text_height = 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,
|
||||||
@@ -65,24 +99,12 @@ public:
|
|||||||
//析构函数 释放图形指针内存
|
//析构函数 释放图形指针内存
|
||||||
~Button();
|
~Button();
|
||||||
|
|
||||||
/*************************************************************************/
|
|
||||||
/********************************Set方法**********************************/
|
|
||||||
/*************************************************************************/
|
|
||||||
//绘制按钮
|
//绘制按钮
|
||||||
void draw() override;
|
void draw() override;
|
||||||
//按钮事件处理
|
//按钮事件处理
|
||||||
|
bool handleEvent(const ExMessage& msg) override;
|
||||||
void handleEvent(const ExMessage& msg) override;
|
|
||||||
|
|
||||||
//设置回调函数
|
//设置回调函数
|
||||||
//************************************
|
|
||||||
// 名称: setOnClickListener | setOnToggleOnListener | setOnToggleOffListener
|
|
||||||
// 全名: Button::setOnClickListener
|
|
||||||
// 访问: public
|
|
||||||
// 返回类型: void
|
|
||||||
// Parameter: const std::function<> & & callback 设置回调函数 传入回调函数名即可,不需要传入(),不需要传入参数,不需要传入返回值
|
|
||||||
// 如果要传入参数,可以使用lambda表达式
|
|
||||||
//************************************
|
|
||||||
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);
|
||||||
@@ -91,29 +113,29 @@ public:
|
|||||||
//设置按钮模式
|
//设置按钮模式
|
||||||
void setbuttonMode(StellarX::ButtonMode mode);
|
void setbuttonMode(StellarX::ButtonMode mode);
|
||||||
//设置圆角矩形椭圆宽度
|
//设置圆角矩形椭圆宽度
|
||||||
int setROUND_RECTANGLEwidth(int width);
|
void setROUND_RECTANGLEwidth(int width);
|
||||||
//设置圆角矩形椭圆高度
|
//设置圆角矩形椭圆高度
|
||||||
int setROUND_RECTANGLEheight(int height);
|
void setROUND_RECTANGLEheight(int height);
|
||||||
//设置按钮填充模式
|
//设置按钮填充模式
|
||||||
void setFillMode(int 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 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);
|
||||||
|
|
||||||
//判断按钮是否被点击
|
//判断按钮是否被点击
|
||||||
bool isClicked() const;
|
bool isClicked() const;
|
||||||
|
|
||||||
/*************************************************************************/
|
|
||||||
/********************************Get方法**********************************/
|
|
||||||
/*************************************************************************/
|
|
||||||
|
|
||||||
//获取按钮文字
|
//获取按钮文字
|
||||||
std::string getButtonText() const;
|
std::string getButtonText() const;
|
||||||
@@ -123,7 +145,7 @@ public:
|
|||||||
//获取按钮形状
|
//获取按钮形状
|
||||||
StellarX::ControlShape getButtonShape() const;
|
StellarX::ControlShape getButtonShape() const;
|
||||||
//获取按钮填充模式
|
//获取按钮填充模式
|
||||||
int getFillMode() const;
|
StellarX::FillMode getFillMode() const;
|
||||||
//获取按钮填充图案
|
//获取按钮填充图案
|
||||||
StellarX::FillStyle getFillIma() const;
|
StellarX::FillStyle getFillIma() const;
|
||||||
//获取按钮填充图像
|
//获取按钮填充图像
|
||||||
@@ -134,13 +156,39 @@ public:
|
|||||||
COLORREF getButtonTextColor() const;
|
COLORREF getButtonTextColor() const;
|
||||||
//获取按钮文字样式
|
//获取按钮文字样式
|
||||||
StellarX::ControlText getButtonTextStyle() const;
|
StellarX::ControlText getButtonTextStyle() const;
|
||||||
|
//获取按钮宽度
|
||||||
|
int getButtonWidth() const;
|
||||||
|
//获取按钮高度
|
||||||
|
int getButtonHeight() const;
|
||||||
|
public:
|
||||||
|
// === Tooltip API===
|
||||||
|
//设置是否启用提示框
|
||||||
|
void enableTooltip(bool on) { tipEnabled = on; if (!on) tipVisible = false; }
|
||||||
|
//设置提示框延时
|
||||||
|
void setTooltipDelay(int ms) { tipDelayMs = (ms < 0 ? 0 : ms); }
|
||||||
|
//设置提示框是否跟随鼠标
|
||||||
|
void setTooltipFollowCursor(bool on) { tipFollowCursor = on; }
|
||||||
|
//设置提示框位置偏移
|
||||||
|
void setTooltipOffset(int dx, int dy) { tipOffsetX = dx; tipOffsetY = dy; }
|
||||||
|
//设置提示框样式
|
||||||
|
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);
|
||||||
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; }
|
||||||
|
void cutButtonText();
|
||||||
|
// 统一隐藏&恢复背景
|
||||||
|
void hideTooltip();
|
||||||
|
// 根据当前 click 状态选择文案
|
||||||
|
void refreshTooltipTextForState();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,51 @@
|
|||||||
#pragma once
|
/*******************************************************************************
|
||||||
|
* @类: Canvas
|
||||||
|
* @摘要: 画布容器控件,用于分组和管理子控件
|
||||||
|
* @描述:
|
||||||
|
* 作为其他控件的父容器,提供统一的背景和边框样式。
|
||||||
|
* 负责将事件传递给子控件并管理它们的绘制顺序。
|
||||||
|
*
|
||||||
|
* @特性:
|
||||||
|
* - 支持四种矩形形状(普通、圆角,各有边框和无边框版本)
|
||||||
|
* - 可自定义填充模式、边框颜色和背景颜色
|
||||||
|
* - 自动管理子控件的生命周期和事件传递
|
||||||
|
* - 支持嵌套容器结构
|
||||||
|
*
|
||||||
|
* @使用场景: 用于分组相关控件、实现复杂布局或作为对话框基础
|
||||||
|
* @所属框架: 星垣(StellarX) GUI框架
|
||||||
|
* @作者: 我在人间做废物
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
#include "Control.h"
|
#include "Control.h"
|
||||||
// 画布容器控件,可以作为其他控件的父容器
|
#include"Table.h"
|
||||||
// 功能:
|
|
||||||
// - 包含和管理子控件
|
|
||||||
// - 将事件传递给子控件
|
|
||||||
// - 提供统一的背景和边框
|
|
||||||
// 使用场景: 用于分组相关控件或实现复杂布局
|
|
||||||
|
|
||||||
class Canvas : public Control
|
class Canvas : public Control
|
||||||
{
|
{
|
||||||
private:
|
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();
|
||||||
public:
|
public:
|
||||||
|
Canvas();
|
||||||
Canvas(int x, int y, int width, int height);
|
Canvas(int x, int y, int width, int height);
|
||||||
~Canvas() {}
|
~Canvas() {}
|
||||||
|
|
||||||
//绘制容器及其子控件
|
//绘制容器及其子控件
|
||||||
void draw() override;
|
void draw() override;
|
||||||
void 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);
|
||||||
//设置容器填充模式
|
//设置容器填充模式
|
||||||
@@ -42,6 +58,15 @@ public:
|
|||||||
void setCanvasLineStyle(StellarX::LineStyle style);
|
void setCanvasLineStyle(StellarX::LineStyle style);
|
||||||
//设置线段宽度
|
//设置线段宽度
|
||||||
void setLinewidth(int width);
|
void setLinewidth(int width);
|
||||||
|
//设置不可见后传递给子控件重写
|
||||||
|
void setIsVisible(bool visible) override;
|
||||||
|
void setDirty(bool dirty) override;
|
||||||
|
void onWindowResize() override;
|
||||||
|
void requestRepaint(Control* parent)override;
|
||||||
|
//获取子控件列表
|
||||||
|
std::vector<std::unique_ptr<Control>>& getControls() { return controls; }
|
||||||
|
private:
|
||||||
|
//用来检查对话框是否模态,此控件不做实现
|
||||||
|
bool model() const override { return false; };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +1,108 @@
|
|||||||
#pragma once
|
/*******************************************************************************
|
||||||
/*********************************************************************
|
* @类: Control
|
||||||
* \文件: Control.h
|
* @摘要: 所有控件的抽象基类,定义通用接口和基础功能
|
||||||
* \描述: 控件基类,所有控件都继承自此类。
|
* @描述:
|
||||||
* 提供了控件的一些基本属性和方法。
|
* 提供控件的基本属性和方法,包括位置、尺寸、重绘标记等。
|
||||||
|
* 实现绘图状态保存和恢复机制,确保控件绘制不影响全局状态。
|
||||||
*
|
*
|
||||||
* \作者: 我在人间做废物
|
* @特性:
|
||||||
* \日期: September 2025
|
* - 定义控件基本属性(坐标、尺寸、脏标记)
|
||||||
*********************************************************************/
|
* - 提供绘图状态管理(saveStyle/restoreStyle)
|
||||||
|
* - 声明纯虚接口(draw、handleEvent等)
|
||||||
|
* - 禁止移动语义,禁止拷贝语义
|
||||||
|
*
|
||||||
|
* @使用场景: 作为所有具体控件类的基类,不直接实例化
|
||||||
|
* @所属框架: 星垣(StellarX) GUI框架
|
||||||
|
* @作者: 我在人间做废物
|
||||||
|
******************************************************************************/
|
||||||
|
#pragma once
|
||||||
|
#ifndef _WIN32_WINNT
|
||||||
|
#define _WIN32_WINNT 0x0600
|
||||||
|
#endif
|
||||||
|
#ifndef WINVER
|
||||||
|
#define WINVER _WIN32_WINNT
|
||||||
|
#endif
|
||||||
|
#include <windows.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <easyx.h>
|
#include <easyx.h>
|
||||||
|
#undef MessageBox
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <initializer_list>
|
|
||||||
#include "CoreTypes.h"
|
#include "CoreTypes.h"
|
||||||
|
|
||||||
class Control
|
class Control
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
int x, y; // 左上角坐标
|
std::string id; // 控件ID
|
||||||
int width, height; // 控件尺寸
|
int localx,x, localy,y; // 左上角坐标
|
||||||
|
int localWidth,width, localHeight,height; // 控件尺寸
|
||||||
|
Control* parent = nullptr; // 父控件
|
||||||
|
bool dirty = true; // 是否重绘
|
||||||
|
bool show = true; // 是否显示
|
||||||
|
|
||||||
|
/* == 布局模式 == */
|
||||||
|
StellarX::LayoutMode layoutMode = StellarX::LayoutMode::Fixed; // 布局模式
|
||||||
|
StellarX::Anchor anchor_1 = StellarX::Anchor::Top; // 锚点
|
||||||
|
StellarX::Anchor anchor_2 = StellarX::Anchor::Left; // 锚点
|
||||||
|
|
||||||
|
/* == 背景快照 == */
|
||||||
|
IMAGE* saveBkImage = nullptr;
|
||||||
|
int saveBkX = 0, saveBkY = 0; // 快照保存起始坐标
|
||||||
|
int saveWidth = 0, saveHeight = 0; // 快照保存尺寸
|
||||||
|
bool hasSnap = false; // 当前是否持有有效快照
|
||||||
|
|
||||||
StellarX::RouRectangle rouRectangleSize; // 圆角矩形椭圆宽度和高度
|
StellarX::RouRectangle rouRectangleSize; // 圆角矩形椭圆宽度和高度
|
||||||
|
|
||||||
LOGFONT currentFont; // 保存当前字体样式和颜色
|
LOGFONT* currentFont = new LOGFONT(); // 保存当前字体样式和颜色
|
||||||
COLORREF currentColor = 0;
|
COLORREF* currentColor = new COLORREF();
|
||||||
COLORREF currentBkColor = 0; // 保存当前填充色
|
COLORREF* currentBkColor = new COLORREF(); // 保存当前填充色
|
||||||
COLORREF currentBorderColor = 0; // 边框颜色
|
COLORREF* currentBorderColor = new COLORREF(); // 边框颜色
|
||||||
LINESTYLE* currentLineStyle = new LINESTYLE; // 保存当前线型
|
LINESTYLE* currentLineStyle = new LINESTYLE(); // 保存当前线型
|
||||||
|
|
||||||
public:
|
|
||||||
Control(const Control&) = delete;
|
Control(const Control&) = delete;
|
||||||
Control& operator=(const Control&) = delete;
|
Control& operator=(const Control&) = delete;
|
||||||
Control(Control&&) = default;
|
Control(Control&&) = delete;
|
||||||
Control& operator=(Control&&) = default;
|
Control& operator=(Control&&) = delete;
|
||||||
|
|
||||||
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)
|
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() {
|
virtual ~Control()
|
||||||
|
{
|
||||||
|
delete currentFont;
|
||||||
|
delete currentColor;
|
||||||
|
delete currentBkColor;
|
||||||
|
delete currentBorderColor;
|
||||||
delete currentLineStyle;
|
delete currentLineStyle;
|
||||||
currentLineStyle = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
currentFont = nullptr;
|
||||||
|
currentColor = nullptr;
|
||||||
|
currentBkColor = nullptr;
|
||||||
|
currentBorderColor = nullptr;
|
||||||
|
currentLineStyle = nullptr;
|
||||||
|
discardBackground();
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
//向上请求重绘
|
||||||
|
virtual void requestRepaint(Control* parent);
|
||||||
|
//根控件/无父时触发重绘
|
||||||
|
virtual void onRequestRepaintAsRoot();
|
||||||
|
protected:
|
||||||
|
//保存背景快照
|
||||||
|
virtual void saveBackground(int x, int y, int w, int h);
|
||||||
|
// putimage 回屏
|
||||||
|
virtual void restBackground();
|
||||||
|
// 释放快照(窗口重绘/尺寸变化后必须作废)
|
||||||
|
void discardBackground();
|
||||||
|
public:
|
||||||
|
//释放快照重新保存,在尺寸变化时更新背景快照避免尺寸变化导致显示错位
|
||||||
|
void updateBackground();
|
||||||
|
//窗口变化丢快照
|
||||||
|
virtual void onWindowResize();
|
||||||
// 获取位置和尺寸
|
// 获取位置和尺寸
|
||||||
int getX() const { return x; }
|
int getX() const { return x; }
|
||||||
int getY() const { return y; }
|
int getY() const { return y; }
|
||||||
@@ -53,10 +110,42 @@ public:
|
|||||||
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 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; }
|
||||||
|
virtual void setWidth(int width) { this->width = width; dirty = true; }
|
||||||
|
virtual void setHeight(int height) { this->height = height; dirty = true; }
|
||||||
|
public:
|
||||||
|
|
||||||
virtual void draw() = 0;
|
virtual void draw() = 0;
|
||||||
virtual void handleEvent(const ExMessage& msg) = 0;
|
virtual bool handleEvent(const ExMessage& msg) = 0;//返回true代表事件已消费
|
||||||
|
//设置是否显示
|
||||||
|
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;
|
||||||
|
//布局
|
||||||
|
void setLayoutMode(StellarX::LayoutMode layoutMode_);
|
||||||
|
void steAnchor(StellarX::Anchor anchor_1, StellarX::Anchor anchor_2);
|
||||||
|
StellarX::Anchor getAnchor_1() const;
|
||||||
|
StellarX::Anchor getAnchor_2() const;
|
||||||
|
StellarX::LayoutMode getLayoutMode() const;
|
||||||
protected:
|
protected:
|
||||||
void saveStyle();
|
void saveStyle();
|
||||||
void restoreStyle();
|
void restoreStyle();
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
/*******************************************************************************
|
||||||
/*******************************************************************************
|
|
||||||
* @文件: CoreTypes.h
|
* @文件: CoreTypes.h
|
||||||
* @摘要: 星垣框架核心类型定义文件
|
* @摘要: 星垣(StellarX)框架核心类型定义文件
|
||||||
* @描述:
|
* @描述:
|
||||||
* 集中定义框架中使用的所有枚举类型和结构体,确保类型一致性。
|
* 集中定义框架中使用的所有枚举类型和结构体,确保类型一致性。
|
||||||
* 这是框架的类型系统基础,所有控件都依赖于此文件中定义的类型。
|
* 这是框架的类型系统基础,所有控件都依赖于此文件中定义的类型。
|
||||||
@@ -9,43 +8,59 @@
|
|||||||
* @作者: 我在人间做废物
|
* @作者: 我在人间做废物
|
||||||
* @日期: September 2025
|
* @日期: September 2025
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
#include"easyX.h"
|
#pragma once
|
||||||
|
|
||||||
|
#include "easyx.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @命名空间: StellarX
|
||||||
|
*
|
||||||
|
* @详细说明:
|
||||||
|
* 集中定义框架中使用的所有枚举类型和结构体,确保类型一致性。
|
||||||
|
* 这是框架的类型系统基础,所有控件都依赖于此文件中定义的类型
|
||||||
|
*
|
||||||
|
* @使用示例:
|
||||||
|
* StellarX::FillStyle::Horizontal - 填充样式
|
||||||
|
*
|
||||||
|
* @备注:
|
||||||
|
* 不用单独包含此头文件,已在StellarX.h中包含,包含唯一对外头文件即可
|
||||||
|
*/
|
||||||
namespace StellarX
|
namespace StellarX
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @枚举类名称: hatchStyle
|
* @枚举类名称: FillStyle
|
||||||
* @功能描述: 用来定义控件填充图案的枚举类
|
* @功能描述: 用来定义控件填充图案的枚举类
|
||||||
*
|
*
|
||||||
* @详细说明:
|
* @详细说明:
|
||||||
* 根据此枚举类可以自定义控件填充图案
|
* 根据此枚举类可以自定义控件填充图案
|
||||||
* 可以在控件初始化时设置填充图案
|
* 可以在控件初始化时设置填充图案
|
||||||
* 根据具体情况选择不同的填充图案
|
* 根据具体情况选择不同的填充图案
|
||||||
* 默认填充图案为水平线
|
* 默认填充图案为水平线
|
||||||
*
|
*
|
||||||
* @取值说明:
|
* @取值说明:
|
||||||
* Horizontal - 水平线
|
* Horizontal - 水平线
|
||||||
* Vertical - 垂直线
|
* Vertical - 垂直线
|
||||||
* FDiagonal - 反斜线
|
* FDiagonal - 反斜线
|
||||||
* BDiagonal - 正斜线
|
* BDiagonal - 正斜线
|
||||||
* Cross - 十字
|
* Cross - 十字
|
||||||
* DiagCross - 网格
|
* DiagCross - 网格
|
||||||
*
|
*
|
||||||
* @使用示例:
|
* @使用示例:
|
||||||
* // 示例代码展示如何使用此枚举类
|
* FillStyle var = FillStyle::Horizontal;
|
||||||
* hatchStyle var = hatchStyle::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
|
||||||
* @功能描述: 用来定义控件填充模式的枚举类
|
* @功能描述: 用来定义控件填充模式的枚举类
|
||||||
@@ -64,22 +79,19 @@ namespace StellarX
|
|||||||
* 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 // 自定义图像填充
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @枚举类名称: linStyle
|
* @枚举类名称: LineStyle
|
||||||
* @功能描述: 此枚举类用来定义控件边框线型
|
* @功能描述: 此枚举类用来定义控件边框线型
|
||||||
*
|
*
|
||||||
* @详细说明:
|
* @详细说明:
|
||||||
@@ -97,13 +109,10 @@ namespace StellarX
|
|||||||
* 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, // 点线
|
||||||
@@ -130,15 +139,12 @@ namespace StellarX
|
|||||||
* bool bItalic = false; - 是否斜体
|
* bool bItalic = false; - 是否斜体
|
||||||
* bool bUnderline = false; - 是否下划线
|
* bool bUnderline = false; - 是否下划线
|
||||||
* bool bStrikeOut = false; - 是否删除线
|
* bool bStrikeOut = false; - 是否删除线
|
||||||
|
|
||||||
* bool operator!=(const ControlText& text);
|
|
||||||
* ControlText& operator=(const ControlText& text
|
|
||||||
*/
|
*/
|
||||||
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; //- 字符旋转角度
|
||||||
@@ -149,7 +155,6 @@ namespace StellarX
|
|||||||
|
|
||||||
bool operator!=(const ControlText& text);
|
bool operator!=(const ControlText& text);
|
||||||
ControlText& operator=(const ControlText& text);
|
ControlText& operator=(const ControlText& text);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -163,18 +168,15 @@ namespace StellarX
|
|||||||
* @取值说明:
|
* @取值说明:
|
||||||
* 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 = ELLIPSE;
|
* ControlShape shape = ControlShape::ELLIPSE;
|
||||||
*
|
*
|
||||||
* @备注:
|
* @备注:
|
||||||
* 按钮类支持所有形状,部分控件只支持部分形状,具体请参考控件类。
|
* 按钮类支持所有形状,部分控件只支持部分形状,具体请参考控件类。
|
||||||
@@ -183,66 +185,205 @@ namespace StellarX
|
|||||||
{
|
{
|
||||||
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
|
|
||||||
* @功能描述: 定义了文本框的两种模式
|
|
||||||
*
|
|
||||||
* @详细说明:
|
|
||||||
* 需要限制文本框是否接受用户输入时使用
|
|
||||||
*
|
|
||||||
* @取值说明:
|
|
||||||
* INPUT_MODE, // 用户可输入模式
|
|
||||||
* READONLY_MODE // 只读模式
|
|
||||||
*
|
|
||||||
* @使用示例:
|
|
||||||
* // 示例代码展示如何使用此枚举类
|
|
||||||
* StellarX::TextBoxmode var = EnumClassName::VALUE1;
|
|
||||||
*
|
|
||||||
* @备注:
|
|
||||||
* 枚举类的特性、与普通枚举的区别
|
|
||||||
*/
|
|
||||||
enum class TextBoxmode
|
|
||||||
{
|
|
||||||
INPUT_MODE, // 用户可输入模式
|
|
||||||
READONLY_MODE // 只读模式
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @枚举名称: ButtonMode
|
* @枚举类名称: TextBoxmode
|
||||||
* @功能描述: brief
|
* @功能描述: 定义了文本框的两种模式
|
||||||
*
|
*
|
||||||
* @详细说明:
|
* @详细说明:
|
||||||
* 根据按钮的工作模式,按钮可以有不同的行为。
|
* 需要限制文本框是否接受用户输入时使用
|
||||||
* 用户可以在具体情况下设置按钮的工作模式。
|
*
|
||||||
*
|
* @取值说明:
|
||||||
* @取值说明:
|
* INPUT_MODE, // 用户可输入模式
|
||||||
* NORMAL = 1, - 普通模式,点击后触发回调,但不会保持状态。
|
* READONLY_MODE // 只读模式
|
||||||
* TOGGLE, - 切换模式,点击后会在选中和未选中之间切换,触发不同的回调函数。
|
*/
|
||||||
* DISABLED - 禁用模式,按钮不可点击,显示为灰色,文本显示删除线。
|
enum class TextBoxmode
|
||||||
*
|
{
|
||||||
* @使用示例:
|
INPUT_MODE, // 用户可输入模式
|
||||||
* Button b1(100, 100, 120, 120, "测试按钮", RGB(128, 0, 0), RGB(255, 9, 9));
|
READONLY_MODE // 只读模式
|
||||||
*
|
};
|
||||||
*/
|
|
||||||
|
/**
|
||||||
|
* @枚举名称: ButtonMode
|
||||||
|
* @功能描述: 定义按钮的工作模式
|
||||||
|
*
|
||||||
|
* @详细说明:
|
||||||
|
* 根据按钮的工作模式,按钮可以有不同的行为。
|
||||||
|
* 用户可以在具体情况下设置按钮的工作模式。
|
||||||
|
*
|
||||||
|
* @取值说明:
|
||||||
|
* NORMAL = 1, - 普通模式,点击后触发回调,但不会保持状态。
|
||||||
|
* TOGGLE, - 切换模式,点击后会在选中和未选中之间切换,触发不同的回调函数。
|
||||||
|
* DISABLED - 禁用模式,按钮不可点击,显示为灰色,文本显示删除线。
|
||||||
|
*
|
||||||
|
* @使用示例:
|
||||||
|
* ButtonMode mode = ButtonMode::NORMAL;
|
||||||
|
*/
|
||||||
enum class ButtonMode
|
enum class ButtonMode
|
||||||
{
|
{
|
||||||
NORMAL = 1, //普通模式,点击后触发回调,但不会保持状态。
|
NORMAL = 1, //普通模式
|
||||||
TOGGLE, //切换模式,点击后会在选中和未选中之间切换,触发不同的回调函数。
|
TOGGLE, //切换模式
|
||||||
DISABLED //禁用模式,按钮不可点击,显示为灰色,文本显示删除线。
|
DISABLED //禁用模式
|
||||||
};
|
};
|
||||||
struct RouRectangle
|
|
||||||
|
/**
|
||||||
|
* @结构体名称: RouRectangle
|
||||||
|
* @功能描述: 定义了控件圆角矩形样式时圆角的椭圆尺寸
|
||||||
|
*
|
||||||
|
* @详细说明:
|
||||||
|
* 需要修改控件圆角矩形样式时的圆角椭圆。
|
||||||
|
*
|
||||||
|
* @成员说明:
|
||||||
|
* int ROUND_RECTANGLEwidth = 20; //构成圆角矩形的圆角的椭圆的宽度。
|
||||||
|
* int ROUND_RECTANGLEheight = 20; //构成圆角矩形的圆角的椭圆的高度。
|
||||||
|
*/
|
||||||
|
struct RouRectangle
|
||||||
{
|
{
|
||||||
int ROUND_RECTANGLEwidth = 20; //构成圆角矩形的圆角的椭圆的宽度。
|
int ROUND_RECTANGLEwidth = 20; //构成圆角矩形的圆角的椭圆的宽度。
|
||||||
int ROUND_RECTANGLEheight = 20; //构成圆角矩形的圆角的椭圆的高度。
|
int ROUND_RECTANGLEheight = 20; //构成圆角矩形的圆角的椭圆的高度。
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
// 消息框类型
|
||||||
|
enum class MessageBoxType
|
||||||
|
{
|
||||||
|
OK, // 只有确定按钮
|
||||||
|
OKCancel, // 确定和取消按钮
|
||||||
|
YesNo, // 是和否按钮
|
||||||
|
YesNoCancel, // 是、否和取消按钮
|
||||||
|
RetryCancel, // 重试和取消按钮
|
||||||
|
AbortRetryIgnore, // 中止、重试和忽略按钮
|
||||||
|
};
|
||||||
|
|
||||||
|
// 消息框返回值
|
||||||
|
enum class MessageBoxResult
|
||||||
|
{
|
||||||
|
OK = 1, // 确定按钮
|
||||||
|
Cancel = 2, // 取消按钮
|
||||||
|
Yes = 6, // 是按钮
|
||||||
|
No = 7, // 否按钮
|
||||||
|
Abort = 3, // 中止按钮
|
||||||
|
Retry = 4, // 重试按钮
|
||||||
|
Ignore = 5 // 忽略按钮
|
||||||
|
};
|
||||||
|
#if 0 //布局管理器相关 —待实现—
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*@枚举名称: LayoutKind
|
||||||
|
* @功能描述 : 定义布局管理类型
|
||||||
|
*
|
||||||
|
*@详细说明 :
|
||||||
|
* 根据布局管理类型,控件可以有不同的布局方式。
|
||||||
|
* 用户可以在具体情况下设置布局管理类型。
|
||||||
|
*
|
||||||
|
* @取值说明 :
|
||||||
|
* Absolute:不管,保持子控件自己的坐标(向后兼容)。
|
||||||
|
* HBox: 水平方向排队;支持固定宽、权重分配、对齐、拉伸。
|
||||||
|
* VBox: 竖直方向排队;同上。
|
||||||
|
* Grid(网格):按行列摆;支持固定/自适应/权重行列;支持跨行/跨列;单元内对齐/拉伸。
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
// 布局类型
|
||||||
|
enum class LayoutKind
|
||||||
|
{
|
||||||
|
Absolute = 1,
|
||||||
|
HBox,
|
||||||
|
VBox,
|
||||||
|
Grid,
|
||||||
|
Flow,
|
||||||
|
Stack
|
||||||
|
};
|
||||||
|
|
||||||
|
// 布局参数
|
||||||
|
struct LayoutParams
|
||||||
|
{
|
||||||
|
// 边距左、右、上、下
|
||||||
|
int marginL = 0, marginR = 0, marginT = 0, marginB = 0;
|
||||||
|
// 固定尺寸(>=0 强制;-1 用控件当前尺寸)
|
||||||
|
int fixedW = -1, fixedH = -1;
|
||||||
|
// 主轴权重(HBox=宽度、VBox=高度、Grid见下)
|
||||||
|
float weight = 0.f;
|
||||||
|
// 对齐(非拉伸时生效)
|
||||||
|
enum Align { Start = 0, Center = 1, End = 2, Stretch = 3 };
|
||||||
|
int alignX = Start; // HBox: 次轴=Y;VBox: 次轴=X;Grid: 单元内
|
||||||
|
int alignY = Start; // Grid :控制单元内垂直(HBox / VBox通常只用 alignX)
|
||||||
|
// Grid 专用(可先不做)
|
||||||
|
int gridRow = 0, gridCol = 0, rowSpan = 1, colSpan = 1;
|
||||||
|
// Flow 专用(可先不做)
|
||||||
|
int flowBreak = 0; // 1=强制换行
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @枚举名称: TabPlacement
|
||||||
|
* @功能描述: 定义了选项卡页签的不同位置
|
||||||
|
*
|
||||||
|
* @详细说明:
|
||||||
|
* 根据选项卡页签的位置,选项卡页签可以有不同的布局方式。
|
||||||
|
*
|
||||||
|
* @成员说明:
|
||||||
|
* Top, - 选项卡页签位于顶部
|
||||||
|
* Bottom, - 选项卡页签位于底部
|
||||||
|
* Left, - 选项卡页签位于左侧
|
||||||
|
* Right - 选项卡页签位于右侧
|
||||||
|
*
|
||||||
|
* @使用示例:
|
||||||
|
* TabPlacement placement = TabPlacement::Top;
|
||||||
|
*/
|
||||||
|
enum class TabPlacement
|
||||||
|
{
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
Left,
|
||||||
|
Right
|
||||||
|
};
|
||||||
|
/*
|
||||||
|
* @枚举名称: LayoutMode
|
||||||
|
* @功能描述: 定义了两种布局模式
|
||||||
|
*
|
||||||
|
* @详细说明:
|
||||||
|
* 根据不同模式,在窗口拉伸时采用不同的布局策略
|
||||||
|
*
|
||||||
|
* @成员说明:
|
||||||
|
* Fixed, - 固定布局
|
||||||
|
* AnchorToEdges - 锚定布局
|
||||||
|
*
|
||||||
|
* @使用示例:
|
||||||
|
* LayoutMode mode = LayoutMode::Fixed;
|
||||||
|
*/
|
||||||
|
enum class LayoutMode
|
||||||
|
{
|
||||||
|
Fixed,
|
||||||
|
AnchorToEdges
|
||||||
|
};
|
||||||
|
/*
|
||||||
|
* @枚举名称: Anchor
|
||||||
|
* @功能描述: 定义了控件相对于窗口锚定的位置
|
||||||
|
*
|
||||||
|
* @详细说明:
|
||||||
|
* 根据不同的锚定位置,有不同的拉伸策略
|
||||||
|
*
|
||||||
|
* @成员说明:
|
||||||
|
* Top, - 锚定上边,控件上边与窗口上侧距离保持不变
|
||||||
|
* Bottom, - 锚定底边,控件底边与窗口底边距离保持不变
|
||||||
|
* Left, - 锚定左边,控件左边与窗口左侧距离保持不变
|
||||||
|
* Right - 锚定右边,控件上边与窗口右侧距离保持不变
|
||||||
|
*
|
||||||
|
* @使用示例:
|
||||||
|
* Anchor a = Anchor::Top;
|
||||||
|
*/
|
||||||
|
enum class Anchor
|
||||||
|
{
|
||||||
|
NoAnchor = 0,
|
||||||
|
Left = 1,
|
||||||
|
Right,
|
||||||
|
Top,
|
||||||
|
Bottom
|
||||||
|
};
|
||||||
|
}
|
||||||
136
include/StellarX/Dialog.h
Normal file
136
include/StellarX/Dialog.h
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* @类: Dialog
|
||||||
|
* @摘要: 模态和非模态对话框控件,提供丰富的消息框功能
|
||||||
|
* @描述:
|
||||||
|
* 实现完整的对话框功能,支持多种按钮组合和异步结果回调。
|
||||||
|
* 自动处理布局、背景保存恢复和生命周期管理。
|
||||||
|
*
|
||||||
|
* @特性:
|
||||||
|
* - 支持六种标准消息框类型(OK、YesNo、YesNoCancel等)
|
||||||
|
* - 模态和非模态两种工作模式
|
||||||
|
* - 自动文本换行和尺寸计算
|
||||||
|
* - 背景保存和恢复机制
|
||||||
|
* - 工厂模式下的去重检测
|
||||||
|
*
|
||||||
|
* @使用场景: 用户提示、确认操作、输入请求等交互场景
|
||||||
|
* @所属框架: 星垣(StellarX) GUI框架
|
||||||
|
* @作者: 我在人间做废物
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include"StellarX.h"
|
||||||
|
|
||||||
|
#define closeButtonWidth 30 //关闭按钮宽度
|
||||||
|
#define closeButtonHeight 20 //关闭按钮高度 同时作为对话框标题栏高度
|
||||||
|
#define functionButtonWidth 70 //按钮宽度
|
||||||
|
#define functionButtonHeight 30 //按钮高度
|
||||||
|
#define buttonMargin 10 //按钮间距
|
||||||
|
|
||||||
|
#define buttonAreaHeight 50 //按钮区域高度
|
||||||
|
#define titleToTextMargin 10 //标题到文本的距离
|
||||||
|
#define textToBorderMargin 10 //文本到边框的距离
|
||||||
|
#define BorderWidth 3 //边框宽度
|
||||||
|
class Dialog : public Canvas
|
||||||
|
{
|
||||||
|
Window& hWnd; //窗口引用
|
||||||
|
|
||||||
|
int textWidth = 0; //文本宽度
|
||||||
|
int textHeight = 0; //文本高度
|
||||||
|
int buttonNum = 0; // 按钮数量
|
||||||
|
|
||||||
|
StellarX::MessageBoxType type = StellarX::MessageBoxType::OK; //对话框类型
|
||||||
|
std::string titleText = "提示"; //标题文本
|
||||||
|
std::unique_ptr<Label> title = nullptr; //标题标签
|
||||||
|
|
||||||
|
std::string message; //提示信息
|
||||||
|
std::vector<std::string> lines; //消息内容按行分割
|
||||||
|
|
||||||
|
|
||||||
|
bool needsInitialization = true; //是否需要初始化
|
||||||
|
bool close = false; //是否关闭
|
||||||
|
bool modal = true; //是否模态
|
||||||
|
|
||||||
|
COLORREF backgroundColor = RGB(240, 240, 240); //背景颜色
|
||||||
|
COLORREF borderColor = RGB(100, 100, 100); //边框颜色
|
||||||
|
|
||||||
|
COLORREF buttonTrueColor = RGB(211, 190, 190); //按钮被点击颜色
|
||||||
|
COLORREF buttonFalseColor = RGB(215, 215, 215); //按钮未被点击颜色
|
||||||
|
COLORREF buttonHoverColor = RGB(224, 224, 224); //按钮悬浮颜色
|
||||||
|
|
||||||
|
|
||||||
|
Button* closeButton = nullptr; //关闭按钮
|
||||||
|
|
||||||
|
StellarX::MessageBoxResult result = StellarX::MessageBoxResult::Cancel; // 对话框结果
|
||||||
|
|
||||||
|
bool shouldClose = false; //是否应该关闭
|
||||||
|
bool isCleaning = false; //是否正在清理
|
||||||
|
bool pendingCleanup = false; //延迟清理
|
||||||
|
public:
|
||||||
|
StellarX::ControlText textStyle; // 字体样式
|
||||||
|
// 清理方法声明
|
||||||
|
void performDelayedCleanup();
|
||||||
|
//工厂模式下调用非模态对话框时用来返回结果
|
||||||
|
std::function<void(StellarX::MessageBoxResult)> resultCallback;
|
||||||
|
//设置非模态获取结果的回调函数
|
||||||
|
void SetResultCallback(std::function<void(StellarX::MessageBoxResult)> cb);
|
||||||
|
//获取对话框消息,用以去重
|
||||||
|
std::string GetCaption() const;
|
||||||
|
//获取对话框消息,用以去重
|
||||||
|
std::string GetText() const;
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
Dialog(Window& hWnd, std::string text, std::string message = "对话框", StellarX::MessageBoxType type = StellarX::MessageBoxType::OK, bool modal = true);
|
||||||
|
~Dialog();
|
||||||
|
//绘制对话框
|
||||||
|
void draw() override;
|
||||||
|
//事件处理
|
||||||
|
bool handleEvent(const ExMessage& msg) override;
|
||||||
|
// 设置标题
|
||||||
|
void SetTitle(const std::string& title);
|
||||||
|
// 设置消息内容
|
||||||
|
void SetMessage(const std::string& message);
|
||||||
|
// 设置对话框类型
|
||||||
|
void SetType(StellarX::MessageBoxType type);
|
||||||
|
// 设置模态属性
|
||||||
|
void SetModal(bool modal);
|
||||||
|
// 设置对话框结果
|
||||||
|
void SetResult(StellarX::MessageBoxResult result);
|
||||||
|
// 获取对话框结果
|
||||||
|
StellarX::MessageBoxResult GetResult() const;
|
||||||
|
|
||||||
|
//获取对话框类型
|
||||||
|
bool model() const override;
|
||||||
|
|
||||||
|
// 显示对话框
|
||||||
|
void Show();
|
||||||
|
// 关闭对话框
|
||||||
|
void Close();
|
||||||
|
//初始化
|
||||||
|
void setInitialization(bool init);
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 初始化按钮
|
||||||
|
void initButtons();
|
||||||
|
// 初始化关闭按钮
|
||||||
|
void initCloseButton();
|
||||||
|
// 初始化标题
|
||||||
|
void initTitle();
|
||||||
|
// 按行分割消息内容
|
||||||
|
void splitMessageLines();
|
||||||
|
// 获取文本大小
|
||||||
|
void getTextSize();
|
||||||
|
//初始化对话框尺寸
|
||||||
|
void initDialogSize();
|
||||||
|
void saveBackground(int x, int y, int w, int h)override;
|
||||||
|
|
||||||
|
void restBackground()override;
|
||||||
|
void addControl(std::unique_ptr<Control> control);
|
||||||
|
|
||||||
|
// 清除所有控件
|
||||||
|
void clearControls();
|
||||||
|
//创建对话框按钮
|
||||||
|
std::unique_ptr<Button> createDialogButton(int x, int y, const std::string& text);
|
||||||
|
void requestRepaint(Control* parent) override;
|
||||||
|
};
|
||||||
43
include/StellarX/MessageBox.h
Normal file
43
include/StellarX/MessageBox.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* @类: MessageBox
|
||||||
|
* @摘要: 消息框工厂类,提供简化的对话框创建接口
|
||||||
|
* @描述:
|
||||||
|
* 封装对话框的创建和显示逻辑,提供静态方法供快速调用。
|
||||||
|
* 支持模态阻塞和非模态回调两种使用方式。
|
||||||
|
*
|
||||||
|
* @特性:
|
||||||
|
* - 静态方法调用,无需实例化
|
||||||
|
* - 自动处理模态和非模态的逻辑差异
|
||||||
|
* - 集成到窗口的对话框管理系统中
|
||||||
|
* - 提供去重机制防止重复对话框
|
||||||
|
*
|
||||||
|
* @使用场景: 快速创建标准消息框,减少样板代码
|
||||||
|
* @所属框架: 星垣(StellarX) GUI框架
|
||||||
|
* @作者: 我在人间做废物
|
||||||
|
******************************************************************************/
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
#include "CoreTypes.h"
|
||||||
|
#include "Dialog.h"
|
||||||
|
#include "Window.h"
|
||||||
|
|
||||||
|
namespace StellarX
|
||||||
|
{
|
||||||
|
class MessageBox
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// 模态:阻塞直到关闭,返回结果
|
||||||
|
static MessageBoxResult showModal(Window& wnd,
|
||||||
|
const std::string& text,
|
||||||
|
const std::string& caption = "提示",
|
||||||
|
MessageBoxType type = MessageBoxType::OK);
|
||||||
|
|
||||||
|
// 非模态:立即返回,通过回调异步获取结果
|
||||||
|
static void showAsync(Window& wnd,
|
||||||
|
const std::string& text,
|
||||||
|
const std::string& caption = "提示",
|
||||||
|
MessageBoxType type = MessageBoxType::OK,
|
||||||
|
std::function<void(MessageBoxResult)> onResult = nullptr);
|
||||||
|
};
|
||||||
|
} // namespace StellarX
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* @文件: StellarX.h
|
* @文件: StellarX.h
|
||||||
* @摘要: 星垣(StellarX) GUI框架 - 主包含头文件
|
* @摘要: 星垣(StellarX) GUI框架 - 主包含头文件
|
||||||
* @版本: v1.0.0
|
* @版本: v2.3.0
|
||||||
* @描述:
|
* @描述:
|
||||||
* 一个为Windows平台打造的轻量级、模块化C++ GUI框架。
|
* 一个为Windows平台打造的轻量级、模块化C++ GUI框架。
|
||||||
* 基于EasyX图形库,提供简洁易用的API和丰富的控件。
|
* 基于EasyX图形库,提供简洁易用的API和丰富的控件。
|
||||||
*
|
*
|
||||||
* 通过包含此单一头文件,即可使用框架的所有功能。
|
* 通过包含此单一头文件,即可使用框架的所有功能。
|
||||||
* 内部包含顺序经过精心设计,确保所有依赖关系正确解析。
|
* 内部包含顺序经过精心设计,确保所有依赖关系正确解析。
|
||||||
*
|
*
|
||||||
* @作者: 我在人间做废物
|
* @作者: 我在人间做废物
|
||||||
* @邮箱: [3150131407@qq.com][]
|
* @邮箱: [3150131407@qq.com] | [ysm3150131407@gmail.com]
|
||||||
* @仓库: https://github.com/Ysm-04/StellarX
|
* @仓库: [https://github.com/Ysm-04/StellarX]
|
||||||
*
|
*
|
||||||
* @许可证: MIT License
|
* @许可证: MIT License
|
||||||
* @版权: Copyright (c) 2025 我在人间做废物
|
* @版权: Copyright (c) 2025 我在人间做废物
|
||||||
@@ -22,16 +22,21 @@
|
|||||||
* @包含顺序:
|
* @包含顺序:
|
||||||
* 1. CoreTypes.h - 基础类型定义
|
* 1. CoreTypes.h - 基础类型定义
|
||||||
* 2. Control.h - 控件基类
|
* 2. Control.h - 控件基类
|
||||||
* 3. 其他具体控件头文件
|
* 3. ...其他具体控件头文件
|
||||||
|
* 4. Dialog:继承自 Canvas(Dialog 为可包含子控件的对话框容器)
|
||||||
|
* 5. MessageBox:对话框工厂,提供便捷的模态/非模态调用方式
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreTypes.h"
|
#include "CoreTypes.h"
|
||||||
#include "Control.h"
|
#include "Control.h"
|
||||||
#include"Button.h"
|
#include"Canvas.h"
|
||||||
#include"Window.h"
|
#include"Window.h"
|
||||||
|
#include"Button.h"
|
||||||
#include"Label.h"
|
#include"Label.h"
|
||||||
#include"TextBox.h"
|
#include"TextBox.h"
|
||||||
#include"Canvas.h"
|
|
||||||
#include"Table.h"
|
#include"Table.h"
|
||||||
|
#include"Dialog.h"
|
||||||
|
#include"MessageBox.h"
|
||||||
|
#include"TabControl.h"
|
||||||
|
|||||||
69
include/StellarX/TabControl.h
Normal file
69
include/StellarX/TabControl.h
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* @类: 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;
|
||||||
|
void setDirty(bool dirty) override;
|
||||||
|
void requestRepaint(Control* parent)override;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
@@ -1,31 +1,44 @@
|
|||||||
#pragma once
|
/*******************************************************************************
|
||||||
|
* @类: Label
|
||||||
|
* @摘要: 简单文本标签控件,用于显示静态文本
|
||||||
|
* @描述:
|
||||||
|
* 提供基本的文本显示功能,支持透明背景和自定义样式。
|
||||||
|
* 不支持用户交互,专注于文本呈现。
|
||||||
|
*
|
||||||
|
* @特性:
|
||||||
|
* - 支持背景透明/不透明模式
|
||||||
|
* - 完整的文本样式控制(字体、颜色、效果)
|
||||||
|
* - 自动适应文本内容
|
||||||
|
* - 轻量级无事件处理开销
|
||||||
|
*
|
||||||
|
* @使用场景: 显示说明文字、标题、状态信息等静态内容
|
||||||
|
* @所属框架: 星垣(StellarX) GUI框架
|
||||||
|
* @作者: 我在人间做废物
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
#include "Control.h"
|
#include "Control.h"
|
||||||
// 标签控件,用于显示文本
|
|
||||||
// 特点:
|
|
||||||
// - 支持背景透明/不透明模式
|
|
||||||
// - 不支持用户交互(无事件处理)
|
|
||||||
// - 自动适应文本内容大小
|
|
||||||
|
|
||||||
class Label : public Control
|
class Label : public Control
|
||||||
{
|
{
|
||||||
std::string text; //标签文本
|
std::string text; //标签文本
|
||||||
COLORREF textColor; //标签文本颜色
|
COLORREF textBkColor; //标签背景颜色
|
||||||
COLORREF textBkColor; //标签背景颜色
|
bool textBkDisap = false; //标签背景是否透明
|
||||||
bool textBkDisap = false; //标签背景是否透明
|
|
||||||
|
|
||||||
//标签事件处理(标签无事件)不实现具体代码
|
//标签事件处理(标签无事件)不实现具体代码
|
||||||
void handleEvent(const ExMessage& msg) override {}
|
bool handleEvent(const ExMessage& msg) 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 draw() override;
|
||||||
|
void hide();
|
||||||
//设置标签背景是否透明
|
//设置标签背景是否透明
|
||||||
void setTextdisap(bool key);
|
void setTextdisap(bool key);
|
||||||
//设置标签文本颜色
|
|
||||||
void setTextColor(COLORREF color);
|
|
||||||
//设置标签背景颜色
|
//设置标签背景颜色
|
||||||
void setTextBkColor(COLORREF color);
|
void setTextBkColor(COLORREF color);
|
||||||
//设置标签文本
|
//设置标签文本
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* @文件: Table.h
|
* @类: Table
|
||||||
* @摘要: 高级表格控件,支持分页显示
|
* @摘要: 高级表格控件,支持分页显示和大数据量展示
|
||||||
* @描述:
|
* @描述:
|
||||||
* 提供完整的数据表格功能,包括表头、数据行、分页和导航按钮。
|
* 提供完整的数据表格功能,包括表头、数据行、分页导航等。
|
||||||
* 自动计算列宽和行高,支持自定义样式
|
* 自动计算列宽行高,支持自定义样式和交互。
|
||||||
*
|
*
|
||||||
* @实现机制:
|
* @特性:
|
||||||
* 1. 使用二维向量存储数据
|
* - 自动分页和页码计算
|
||||||
* 2. 通过分页算法计算显示范围
|
* - 可配置的每页行数
|
||||||
* 3. 使用内部按钮和标签实现分页UI
|
* - 自定义边框样式和填充模式
|
||||||
* 4. 通过背景缓存优化渲染性能
|
* - 翻页按钮和页码显示
|
||||||
|
* - 背景缓存优化渲染性能
|
||||||
*
|
*
|
||||||
|
* @使用场景: 数据展示、报表生成、记录浏览等表格需求
|
||||||
* @所属框架: 星垣(StellarX) GUI框架
|
* @所属框架: 星垣(StellarX) GUI框架
|
||||||
* @作者: 我在人间做废物
|
* @作者: 我在人间做废物
|
||||||
* @日期: September 2025
|
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@@ -21,6 +22,34 @@
|
|||||||
#include "Button.h"
|
#include "Button.h"
|
||||||
#include "Label.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 // 按钮垂直 padding(initButton)
|
||||||
|
#define TABLE_BTN_TEXT_PAD_V 8 // 计算页脚高度时的按钮文字垂直 padding(initTextWaH)
|
||||||
|
#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
|
class Table :public Control
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
@@ -28,21 +57,19 @@ private:
|
|||||||
std::vector<std::string> headers; // 表格表头
|
std::vector<std::string> headers; // 表格表头
|
||||||
std::string pageNumtext = "页码标签"; // 页码标签文本
|
std::string pageNumtext = "页码标签"; // 页码标签文本
|
||||||
|
|
||||||
int tableBorderWidth = 1; // 边框宽度
|
int tableBorderWidth = TABLE_DEFAULT_BORDER_WIDTH; // 边框宽度
|
||||||
|
|
||||||
std::vector<int> colWidths; // 每列的宽度
|
std::vector<int> colWidths; // 每列的宽度
|
||||||
std::vector<int> lineHeights; // 每行的高度
|
std::vector<int> lineHeights; // 每行的高度
|
||||||
|
|
||||||
IMAGE* saveBkImage = nullptr;
|
int rowsPerPage = TABLE_DEFAULT_ROWS_PER_PAGE; // 每页显示的行数
|
||||||
|
|
||||||
int rowsPerPage = 5; // 每页显示的行数
|
|
||||||
int currentPage = 1; // 当前页码
|
int currentPage = 1; // 当前页码
|
||||||
int totalPages = 1; // 总页数
|
int totalPages = 1; // 总页数
|
||||||
|
|
||||||
bool isShowPageButton = true; // 是否显示翻页按钮
|
bool isShowPageButton = true; // 是否显示翻页按钮
|
||||||
bool dirty = true; // 是否需要重绘
|
|
||||||
bool isNeedDrawHeaders = true; // 是否需要绘制表头
|
bool isNeedDrawHeaders = true; // 是否需要绘制表头
|
||||||
bool isNeedCellSize = true; // 是否需要计算单元格尺寸
|
bool isNeedCellSize = true; // 是否需要计算单元格尺寸
|
||||||
|
bool isNeedButtonAndPageNum = true; // 是否需要计算翻页按钮和页码信息
|
||||||
|
|
||||||
Button* prevButton = nullptr; // 上一页按钮
|
Button* prevButton = nullptr; // 上一页按钮
|
||||||
Button* nextButton = nullptr; // 下一页按钮
|
Button* nextButton = nullptr; // 下一页按钮
|
||||||
@@ -56,8 +83,8 @@ private:
|
|||||||
|
|
||||||
StellarX::FillMode tableFillMode = StellarX::FillMode::Solid; //填充模式
|
StellarX::FillMode tableFillMode = StellarX::FillMode::Solid; //填充模式
|
||||||
StellarX::LineStyle tableLineStyle = StellarX::LineStyle::Solid; // 线型
|
StellarX::LineStyle tableLineStyle = StellarX::LineStyle::Solid; // 线型
|
||||||
COLORREF tableBorderClor = RGB(0, 0, 0); // 表格边框颜色
|
COLORREF tableBorderClor = TABLE_DEFAULT_BORDER_COLOR; // 表格边框颜色
|
||||||
COLORREF tableBkClor = RGB(255, 255, 255); // 表格背景颜色
|
COLORREF tableBkClor = TABLE_DEFAULT_BG_COLOR; // 表格背景颜色
|
||||||
|
|
||||||
void initTextWaH(); //初始化文本像素宽度和高度
|
void initTextWaH(); //初始化文本像素宽度和高度
|
||||||
void initButton(); //初始化翻页按钮
|
void initButton(); //初始化翻页按钮
|
||||||
@@ -67,9 +94,13 @@ private:
|
|||||||
void drawHeader(); //绘制表头
|
void drawHeader(); //绘制表头
|
||||||
void drawPageNum(); //绘制页码信息
|
void drawPageNum(); //绘制页码信息
|
||||||
void drawButton(); //绘制翻页按钮
|
void drawButton(); //绘制翻页按钮
|
||||||
|
private:
|
||||||
|
//用来检查对话框是否模态,此控件不做实现
|
||||||
|
bool model() const override { return false; };
|
||||||
public:
|
public:
|
||||||
StellarX::ControlText textStyle; // 文本样式
|
StellarX::ControlText textStyle; // 文本样式
|
||||||
|
void setWidth(int width) override;
|
||||||
|
void setHeight(int height) override;
|
||||||
public:
|
public:
|
||||||
Table(int x, int y);
|
Table(int x, int y);
|
||||||
~Table();
|
~Table();
|
||||||
@@ -77,15 +108,13 @@ public:
|
|||||||
// 绘制表格
|
// 绘制表格
|
||||||
void draw() override;
|
void draw() override;
|
||||||
//事件处理
|
//事件处理
|
||||||
void handleEvent(const ExMessage& msg) override;
|
bool handleEvent(const ExMessage& msg) override;
|
||||||
|
|
||||||
//************************** 设置属性 *****************************/
|
|
||||||
|
|
||||||
//设置表头
|
//设置表头
|
||||||
void setHeaders(std::initializer_list<std::string> headers);
|
void setHeaders(std::initializer_list<std::string> headers);
|
||||||
//设置表格数据
|
//设置表格数据
|
||||||
void setData(const std::vector<std::string>& data);
|
void setData(std::vector<std::string> data);
|
||||||
void setData(const std::initializer_list<std::vector<std::string>>& data);
|
void setData(std::initializer_list<std::vector<std::string>> data);
|
||||||
//设置每页显示的行数
|
//设置每页显示的行数
|
||||||
void setRowsPerPage(int rows);
|
void setRowsPerPage(int rows);
|
||||||
//设置是否显示翻页按钮
|
//设置是否显示翻页按钮
|
||||||
@@ -100,6 +129,8 @@ public:
|
|||||||
void setTableLineStyle(StellarX::LineStyle style);
|
void setTableLineStyle(StellarX::LineStyle style);
|
||||||
//设置边框宽度
|
//设置边框宽度
|
||||||
void setTableBorderWidth(int width);
|
void setTableBorderWidth(int width);
|
||||||
|
//窗口变化丢快照+标脏
|
||||||
|
void onWindowResize() override;
|
||||||
|
|
||||||
//************************** 获取属性 *****************************/
|
//************************** 获取属性 *****************************/
|
||||||
|
|
||||||
@@ -125,6 +156,9 @@ public:
|
|||||||
std::vector<std::vector<std::string>> getData() const;
|
std::vector<std::vector<std::string>> getData() const;
|
||||||
//获取表格边框宽度
|
//获取表格边框宽度
|
||||||
int getTableBorderWidth() const;
|
int getTableBorderWidth() const;
|
||||||
|
//获取表格尺寸
|
||||||
|
int getTableWidth() const;
|
||||||
|
int getTableHeight() const;
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,20 +1,31 @@
|
|||||||
#pragma once
|
/*******************************************************************************
|
||||||
|
* @类: TextBox
|
||||||
|
* @摘要: 文本框控件,支持输入和只读两种模式
|
||||||
|
* @描述:
|
||||||
|
* 提供文本输入和显示功能,集成EasyX的InputBox用于数据输入。
|
||||||
|
* 支持有限的形状样式和视觉定制。
|
||||||
|
*
|
||||||
|
* @特性:
|
||||||
|
* - 两种工作模式:输入模式和只读模式
|
||||||
|
* - 最大字符长度限制
|
||||||
|
* - 集成系统输入框简化文本输入
|
||||||
|
* - 支持四种矩形形状变体
|
||||||
|
*
|
||||||
|
* @使用场景: 数据输入、文本显示、表单字段等
|
||||||
|
* @所属框架: 星垣(StellarX) GUI框架
|
||||||
|
* @作者: 我在人间做废物
|
||||||
|
******************************************************************************/
|
||||||
|
#pragma once
|
||||||
#include "Control.h"
|
#include "Control.h"
|
||||||
|
|
||||||
// 文本框控件,支持输入和只读两种模式
|
|
||||||
// 特殊说明:
|
|
||||||
// - 在INPUT_MODE下点击会调用EasyX的InputBox
|
|
||||||
// - 在READONLY_MODE下点击会显示信息对话框
|
|
||||||
// - 最大字符长度受maxCharLen限制
|
|
||||||
|
|
||||||
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 dirty = true; //是否重绘
|
|
||||||
bool click = false; //是否点击
|
bool click = false; //是否点击
|
||||||
int 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); //边框颜色
|
||||||
|
|
||||||
@@ -23,22 +34,26 @@ public:
|
|||||||
|
|
||||||
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;
|
||||||
void handleEvent(const ExMessage& msg) override;
|
bool handleEvent(const ExMessage& msg) override;
|
||||||
//设置模式
|
//设置模式
|
||||||
|
|
||||||
void setMode(StellarX::TextBoxmode mode);
|
void setMode(StellarX::TextBoxmode mode);
|
||||||
//设置可输入最大字符长度
|
//设置可输入最大字符长度
|
||||||
void setMaxCharLen(int 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);
|
||||||
|
|
||||||
//获取文本
|
//获取文本
|
||||||
std::string getText() const;
|
std::string getText() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//用来检查对话框是否模态,此控件不做实现
|
||||||
|
bool model() const override { return false; };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,53 +1,110 @@
|
|||||||
#pragma once
|
|
||||||
#include "Control.h"
|
/**
|
||||||
//窗口模式
|
* Window(头文件)
|
||||||
//#define EX_DBLCLKS 1 - 在绘图窗口中支持鼠标双击事件。
|
|
||||||
//#define EX_NOCLOSE 2 - 禁用绘图窗口的关闭按钮。
|
|
||||||
//#define EX_NOMINIMIZE 3 - 禁用绘图窗口的最小化按钮。
|
|
||||||
//#define EX_SHOWCONSOLE 4 - 显示控制台窗口。
|
|
||||||
/*******************************************************************************
|
|
||||||
* @类: Window
|
|
||||||
* @摘要: 应用程序主窗口类
|
|
||||||
* @描述:
|
|
||||||
* 负责创建和管理应用程序的主窗口,是所有控件的根容器。
|
|
||||||
* 处理消息循环、事件分发和窗口生命周期管理。
|
|
||||||
*
|
*
|
||||||
* @重要说明:
|
* 设计目标:
|
||||||
* - 使用 initgraph() 创建窗口
|
* - 提供一个基于 Win32 + EasyX 的“可拉伸且稳定不抖”的窗口容器。
|
||||||
* - 使用 BeginBatchDraw()/EndBatchDraw() 实现双缓冲
|
* - 通过消息过程子类化(WndProcThunk)接管关键消息(WM_SIZING/WM_SIZE/...)。
|
||||||
* - 使用 getmessage() 处理消息循环
|
* - 将“几何变化记录(pendingW/H)”与“统一收口重绘(needResizeDirty)”解耦。
|
||||||
******************************************************************************/
|
*
|
||||||
|
* 关键点(与 .cpp 中实现对应):
|
||||||
|
* - isSizing:处于交互拉伸阶段时,冻结重绘;松手后统一重绘,防止抖动。
|
||||||
|
* - WM_SIZING:只做“最小尺寸夹紧”,不回滚矩形、不做对齐;把其余交给系统。
|
||||||
|
* - WM_GETMINMAXINFO:按最小“客户区”换算到“窗口矩形”,提供系统层最小轨迹值。
|
||||||
|
* - runEventLoop:只记录 WM_SIZE 的新尺寸;真正绘制放在 needResizeDirty 时集中处理。
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Control.h"
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
class Window
|
class Window
|
||||||
{
|
{
|
||||||
int width; //窗口宽度
|
// —— 尺寸状态 ——(绘制尺寸与待应用尺寸分离;收口时一次性更新)
|
||||||
int height; //窗口高度
|
int width; // 当前有效宽(已应用到画布/控件的客户区宽)
|
||||||
int windowMode = NULL; //窗口模式
|
int height; // 当前有效高(已应用到画布/控件的客户区高)
|
||||||
HWND hWnd = NULL; //窗口句柄
|
int localwidth; // 基准宽(创建时的宽度)
|
||||||
std::string headline; //窗口标题
|
int localheight; // 基准高(创建是的高度)
|
||||||
COLORREF wBkcolor = BLACK; //窗口背景
|
int pendingW; // 待应用宽(WM_SIZE/拉伸中记录用)
|
||||||
IMAGE* background = nullptr; //窗口背景图片
|
int pendingH; // 待应用高
|
||||||
std::vector<std::unique_ptr<Control>> controls; //控件管理
|
int minClientW; // 业务设定的最小客户区宽(用于 GETMINMAXINFO 与 SIZING 夹紧)
|
||||||
|
int minClientH; // 业务设定的最小客户区高
|
||||||
|
int windowMode = NULL; // EasyX 初始化模式(EX_SHOWCONSOLE/EX_TOPMOST/...)
|
||||||
|
bool needResizeDirty = false; // 统一收口重绘标志(置位后在事件环末尾处理)
|
||||||
|
bool isSizing = false; // 是否处于拖拽阶段(ENTER/EXIT SIZEMOVE 切换)
|
||||||
|
|
||||||
|
// —— 原生窗口句柄与子类化钩子 ——(子类化 EasyX 的窗口过程以拦截关键消息)
|
||||||
|
HWND hWnd = NULL; // EasyX 初始化后的窗口句柄
|
||||||
|
WNDPROC oldWndProc = nullptr; // 保存旧过程(CallWindowProc 回落)
|
||||||
|
bool procHooked = false; // 避免重复子类化
|
||||||
|
static LRESULT CALLBACK WndProcThunk(HWND h, UINT m, WPARAM w, LPARAM l); // 静态过程分发到 this
|
||||||
|
|
||||||
|
// —— 绘制相关 ——(是否使用合成双缓冲、窗口标题、背景等)
|
||||||
|
bool useComposited = true; // 是否启用 WS_EX_COMPOSITED(部分机器可能增加一帧观感延迟)
|
||||||
|
std::string headline; // 窗口标题文本
|
||||||
|
COLORREF wBkcolor = BLACK; // 纯色背景(无背景图时使用)
|
||||||
|
IMAGE* background = nullptr; // 背景图对象指针(存在时优先绘制)
|
||||||
|
std::string bkImageFile; // 背景图文件路径(loadimage 用)
|
||||||
|
|
||||||
|
// —— 控件/对话框 ——(容器内的普通控件与非模态对话框)
|
||||||
|
std::vector<std::unique_ptr<Control>> controls;
|
||||||
|
std::vector<std::unique_ptr<Control>> dialogs;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Window(int width, int height, int mode);
|
bool dialogClose = false; // 项目内使用的状态位
|
||||||
Window(int width, int height, int mode, COLORREF bkcloc);
|
|
||||||
Window(int width, int height, int mode , COLORREF bkcloc, std::string headline = "窗口");
|
// —— 构造/析构 ——(仅初始化成员;实际样式与子类化在 draw() 中完成)
|
||||||
~Window();
|
Window(int width, int height, int mode);
|
||||||
//绘制窗口
|
Window(int width, int height, int mode, COLORREF bkcloc);
|
||||||
void draw();
|
Window(int width, int height, int mode, COLORREF bkcloc, std::string headline);
|
||||||
void draw(std::string pImgFile);
|
~Window();
|
||||||
//事件循环
|
|
||||||
void runEventLoop();
|
// —— 绘制与事件循环 ——(draw* 完成一次全量绘制;runEventLoop 驱动事件与统一收口)
|
||||||
//设置窗口背景图片
|
void draw(); // 纯色背景版本
|
||||||
|
void draw(std::string pImgFile); // 背景图版本
|
||||||
void setBkImage(std::string pImgFile);
|
int runEventLoop(); // 主事件循环(PeekMessage + 统一收口重绘)
|
||||||
//设置窗口背景颜色
|
|
||||||
void setBkcolor(COLORREF c);
|
// —— 背景/标题设置 ——(更换背景、背景色与标题;立即触发一次批量绘制)
|
||||||
//设置窗口标题
|
void setBkImage(std::string pImgFile);
|
||||||
void setHeadline(std::string headline);
|
void setBkcolor(COLORREF c);
|
||||||
//添加控件
|
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);
|
||||||
|
bool hasNonModalDialogWithCaption(const std::string& caption, const std::string& message) const;
|
||||||
|
|
||||||
|
// —— 访问器 ——(只读接口,供外部查询当前窗口/标题/背景等)
|
||||||
|
HWND getHwnd() const;
|
||||||
|
int getWidth() const;
|
||||||
|
int getHeight() const;
|
||||||
|
std::string getHeadline() const;
|
||||||
|
COLORREF getBkcolor() const;
|
||||||
|
IMAGE* getBkImage() const;
|
||||||
|
std::string getBkImageFile() const;
|
||||||
|
std::vector<std::unique_ptr<Control>>& getControls();
|
||||||
|
|
||||||
|
// —— 配置开关 ——(动态调整最小客户区、合成双缓冲)
|
||||||
|
inline void setMinClientSize(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:
|
||||||
|
void adaptiveLayout(std::unique_ptr<Control>& c,const int finalH, const int finalW);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
597
src/Button.cpp
597
src/Button.cpp
@@ -1,4 +1,4 @@
|
|||||||
#include "StellarX/Button.h"
|
#include "Button.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)
|
||||||
@@ -17,9 +17,134 @@ Button::Button(int x, int y, int width, int height, const std::string text, COLO
|
|||||||
{
|
{
|
||||||
initButton(text, mode, shape, ct, cf, ch);
|
initButton(text, mode, shape, ct, cf, ch);
|
||||||
}
|
}
|
||||||
|
// ====== GBK/MBCS 安全:字符边界与省略号裁切 ======
|
||||||
|
static inline int gbk_char_len(const std::string& s, size_t i)
|
||||||
|
{
|
||||||
|
unsigned char b = (unsigned char)s[i];
|
||||||
|
if (b <= 0x7F) return 1; // ASCII
|
||||||
|
if (b >= 0x81 && b <= 0xFE && i + 1 < s.size())
|
||||||
|
{
|
||||||
|
unsigned char b2 = (unsigned char)s[i + 1];
|
||||||
|
if (b2 >= 0x40 && b2 <= 0xFE && b2 != 0x7F) return 2; // 合法双字节
|
||||||
|
}
|
||||||
|
return 1; // 容错
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void rtrim_spaces_gbk(std::string& s)
|
||||||
|
{
|
||||||
|
while (!s.empty() && s.back() == ' ') s.pop_back(); // ASCII 空格
|
||||||
|
while (s.size() >= 2)
|
||||||
|
{ // 全角空格 A1 A1
|
||||||
|
unsigned char a = (unsigned char)s[s.size() - 2];
|
||||||
|
unsigned char b = (unsigned char)s[s.size() - 1];
|
||||||
|
if (a == 0xA1 && b == 0xA1) s.resize(s.size() - 2);
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool is_ascii_only(const std::string& s)
|
||||||
|
{
|
||||||
|
for (unsigned char c : s) if (c > 0x7F) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool is_word_boundary_char(unsigned char c)
|
||||||
|
{
|
||||||
|
return c == ' ' || c == '-' || c == '_' || c == '/' || c == '\\' || c == '.' || c == ':';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 英文优先策略:优先在“词边界”回退,再退化到逐字符;省略号为 "..."
|
||||||
|
static std::string ellipsize_ascii_pref(const std::string& text, int maxW)
|
||||||
|
{
|
||||||
|
if (maxW <= 0) return "";
|
||||||
|
if (textwidth(LPCTSTR(text.c_str())) <= maxW) return text;
|
||||||
|
|
||||||
|
const std::string ell = "...";
|
||||||
|
int ellW = textwidth(LPCTSTR(ell.c_str()));
|
||||||
|
if (ellW > maxW)
|
||||||
|
{ // 连 ... 都放不下
|
||||||
|
std::string e = ell;
|
||||||
|
while (!e.empty() && textwidth(LPCTSTR(e.c_str())) > maxW) e.pop_back();
|
||||||
|
return e; // 可能是 ".."、"." 或 ""
|
||||||
|
}
|
||||||
|
const int limit = maxW - ellW;
|
||||||
|
|
||||||
|
// 先找到能放下的最长前缀
|
||||||
|
size_t i = 0, lastFit = 0;
|
||||||
|
while (i < text.size())
|
||||||
|
{
|
||||||
|
int clen = gbk_char_len(text, i);
|
||||||
|
size_t j = text.size() < i + (size_t)clen ? text.size() : i + (size_t)clen;
|
||||||
|
int w = textwidth(LPCTSTR(text.substr(0, j).c_str()));
|
||||||
|
if (w <= limit) { lastFit = j; i = j; }
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
if (lastFit == 0) return ell;
|
||||||
|
|
||||||
|
// 在已适配前缀范围内,向左找最近的词边界
|
||||||
|
size_t cutPos = lastFit;
|
||||||
|
for (size_t k = lastFit; k > 0; --k)
|
||||||
|
{
|
||||||
|
unsigned char c = (unsigned char)text[k - 1];
|
||||||
|
if (c <= 0x7F && is_word_boundary_char(c)) { cutPos = k - 1; break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string head = text.substr(0, cutPos);
|
||||||
|
rtrim_spaces_gbk(head);
|
||||||
|
head += ell;
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 中文优先策略:严格逐“字符”(1/2字节)回退;省略号用全角 "…"
|
||||||
|
static std::string ellipsize_cjk_pref(const std::string& text, int maxW, const char* ellipsis = "…")
|
||||||
|
{
|
||||||
|
if (maxW <= 0) return "";
|
||||||
|
if (textwidth(LPCTSTR(text.c_str())) <= maxW) return text;
|
||||||
|
|
||||||
|
std::string ell = ellipsis ? ellipsis : "…";
|
||||||
|
int ellW = textwidth(LPCTSTR(ell.c_str()));
|
||||||
|
if (ellW > maxW)
|
||||||
|
{ // 连省略号都放不下
|
||||||
|
std::string e = ell;
|
||||||
|
while (!e.empty() && textwidth(LPCTSTR(e.c_str())) > maxW) e.pop_back();
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
const int limit = maxW - ellW;
|
||||||
|
|
||||||
|
size_t i = 0, lastFit = 0;
|
||||||
|
while (i < text.size())
|
||||||
|
{
|
||||||
|
int clen = gbk_char_len(text, i);
|
||||||
|
size_t j = text.size() < i + (size_t)clen ? text.size() : i + (size_t)clen;
|
||||||
|
int w = textwidth(LPCTSTR(text.substr(0, j).c_str()));
|
||||||
|
if (w <= limit) { lastFit = j; i = j; }
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
if (lastFit == 0) return ell;
|
||||||
|
|
||||||
|
std::string head = text.substr(0, lastFit);
|
||||||
|
rtrim_spaces_gbk(head);
|
||||||
|
head += ell;
|
||||||
|
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)
|
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->text = text;
|
||||||
this->mode = mode;
|
this->mode = mode;
|
||||||
this->shape = shape;
|
this->shape = shape;
|
||||||
@@ -28,115 +153,145 @@ void Button::initButton(const std::string text, StellarX::ButtonMode mode, Stell
|
|||||||
this->buttonHoverColor = ch;
|
this->buttonHoverColor = ch;
|
||||||
this->click = false;
|
this->click = false;
|
||||||
this->hover = false;
|
this->hover = false;
|
||||||
|
|
||||||
|
// === Tooltip 默认:文本=按钮文本;白底黑字;不透明;用当前按钮字体样式 ===
|
||||||
|
tipTextClick = tipTextOn = tipTextOff = this->text;
|
||||||
|
tipLabel.setText(tipTextClick);
|
||||||
|
tipLabel.textStyle.color = (RGB(167, 170, 172));
|
||||||
|
tipLabel.setTextBkColor(RGB(255, 255, 255));
|
||||||
|
tipLabel.setTextdisap(false);
|
||||||
|
tipLabel.textStyle = this->textStyle; // 复用按钮字体样式
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Button::~Button()
|
Button::~Button()
|
||||||
{
|
{
|
||||||
if (buttonFileIMAGE)
|
if (buttonFileIMAGE)
|
||||||
delete buttonFileIMAGE;
|
delete buttonFileIMAGE;
|
||||||
buttonFileIMAGE = nullptr;
|
buttonFileIMAGE = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Button::draw()
|
void Button::draw()
|
||||||
{
|
{
|
||||||
if (dirty)
|
if (!dirty || !show)return;
|
||||||
{
|
|
||||||
//保存当前样式和颜色
|
|
||||||
saveStyle();
|
|
||||||
|
|
||||||
if (StellarX::ButtonMode::DISABLED == mode) //设置禁用按钮色
|
//保存当前样式和颜色
|
||||||
|
saveStyle();
|
||||||
|
|
||||||
|
if (StellarX::ButtonMode::DISABLED == mode) //设置禁用按钮色
|
||||||
|
{
|
||||||
|
setfillcolor(DISABLEDCOLOUR);
|
||||||
|
textStyle.bStrikeOut = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 点击状态优先级最高,然后是悬停状态,最后是默认状态
|
||||||
|
COLORREF col = click ? buttonTrueColor : (hover ? buttonHoverColor : buttonFalseColor);
|
||||||
|
setfillcolor(col);
|
||||||
|
}
|
||||||
|
//
|
||||||
|
//设置字体背景色透明
|
||||||
|
setbkmode(TRANSPARENT);
|
||||||
|
//边框颜色
|
||||||
|
setlinecolor(buttonBorderColor);
|
||||||
|
|
||||||
|
//设置字体颜色
|
||||||
|
settextcolor(textStyle.color);
|
||||||
|
//设置字体样式
|
||||||
|
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
||||||
|
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
||||||
|
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
|
||||||
|
|
||||||
|
if (needCutText)
|
||||||
|
cutButtonText();
|
||||||
|
|
||||||
|
//获取字符串像素高度和宽度
|
||||||
|
if ((this->oldtext_width != this->text_width || this->oldtext_height != this->text_height)
|
||||||
|
|| (-1 == oldtext_width && oldtext_height == -1))
|
||||||
|
{
|
||||||
|
if (isUseCutText)
|
||||||
{
|
{
|
||||||
setfillcolor(RGB(96, 96, 96));
|
this->oldtext_width = this->text_width = textwidth(LPCTSTR(this->cutText.c_str()));
|
||||||
textStyle.bStrikeOut = 1;
|
this->oldtext_height = this->text_height = textheight(LPCTSTR(this->cutText.c_str()));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
// 确保点击状态的颜色正确显示
|
|
||||||
// 点击状态优先级最高,然后是悬停状态,最后是默认状态
|
|
||||||
if (click)
|
|
||||||
setfillcolor(buttonTrueColor);
|
|
||||||
else if (hover)
|
|
||||||
setfillcolor(buttonHoverColor);
|
|
||||||
else
|
|
||||||
setfillcolor(buttonFalseColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
//设置字体背景色透明
|
|
||||||
setbkmode(TRANSPARENT);
|
|
||||||
//边框颜色
|
|
||||||
setlinecolor(buttonBorderColor);
|
|
||||||
if (this->textStyle != oldStyle)
|
|
||||||
{
|
|
||||||
//设置字体颜色
|
|
||||||
settextcolor(textStyle.color);
|
|
||||||
|
|
||||||
//设置字体样式
|
|
||||||
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
|
||||||
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
|
||||||
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); //设置字体样式
|
|
||||||
}
|
|
||||||
//设置按钮填充模式
|
|
||||||
setfillstyle(buttonFillMode, (int)buttonFillIma, buttonFileIMAGE);
|
|
||||||
|
|
||||||
//获取字符串像素高度和宽度
|
|
||||||
if ((this->oldtext_width != this->text_width || this->oldtext_height != this->text_height)
|
|
||||||
|| (-1 == oldtext_width && oldtext_height == -1))
|
|
||||||
{
|
{
|
||||||
this->oldtext_width = this->text_width = textwidth(LPCTSTR(this->text.c_str()));
|
this->oldtext_width = this->text_width = textwidth(LPCTSTR(this->text.c_str()));
|
||||||
this->oldtext_height = this->text_height = textheight(LPCTSTR(this->text.c_str()));
|
this->oldtext_height = this->text_height = textheight(LPCTSTR(this->text.c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//根据按钮形状绘制
|
|
||||||
switch (shape)
|
|
||||||
{
|
|
||||||
case StellarX::ControlShape::RECTANGLE:
|
|
||||||
fillrectangle(x, y, x + width, y + height);//有边框填充矩形
|
|
||||||
outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
|
||||||
break;
|
|
||||||
case StellarX::ControlShape::B_RECTANGLE:
|
|
||||||
solidrectangle(x, y, x + width, y + height);//无边框填充矩形
|
|
||||||
outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
|
||||||
break;
|
|
||||||
case StellarX::ControlShape::ROUND_RECTANGLE:
|
|
||||||
fillroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);//有边框填充圆角矩形
|
|
||||||
outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
|
||||||
break;
|
|
||||||
case StellarX::ControlShape::B_ROUND_RECTANGLE:
|
|
||||||
solidroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);//无边框填充圆角矩形
|
|
||||||
outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
|
||||||
break;
|
|
||||||
case StellarX::ControlShape::CIRCLE:
|
|
||||||
fillcircle(x + width / 2, y + height / 2, min(width, height) / 2);//有边框填充圆形
|
|
||||||
outtextxy(x + width / 2 - text_width / 2, y + height / 2 - text_height / 2, LPCTSTR(text.c_str()));
|
|
||||||
break;
|
|
||||||
case StellarX::ControlShape::B_CIRCLE:
|
|
||||||
solidcircle(x + width / 2, y + height / 2, min(width, height) / 2);//无边框填充圆形
|
|
||||||
outtextxy(x + width / 2 - text_width / 2, y + height / 2 - text_height / 2, LPCTSTR(text.c_str()));
|
|
||||||
break;
|
|
||||||
case StellarX::ControlShape::ELLIPSE:
|
|
||||||
fillellipse(x, y, x + width, y + height);//有边框填充椭圆
|
|
||||||
outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
|
||||||
break;
|
|
||||||
case StellarX::ControlShape::B_ELLIPSE:
|
|
||||||
solidellipse(x, y, x + width, y + height);//无边框填充椭圆
|
|
||||||
outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
restoreStyle();//恢复默认字体样式和颜色
|
|
||||||
|
|
||||||
dirty = false; //标记按钮不需要重绘
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//设置按钮填充模式
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
case StellarX::ControlShape::RECTANGLE://有边框填充矩形
|
||||||
|
fillrectangle(x, y, x + width, y + height);
|
||||||
|
isUseCutText ? outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(cutText.c_str()))
|
||||||
|
: outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||||
|
break;
|
||||||
|
case StellarX::ControlShape::B_RECTANGLE://无边框填充矩形
|
||||||
|
solidrectangle(x, y, x + width, y + height);
|
||||||
|
isUseCutText ? outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(cutText.c_str()))
|
||||||
|
: outtextxy((x + (width - text_width) / 2), (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);
|
||||||
|
isUseCutText ? outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(cutText.c_str()))
|
||||||
|
: outtextxy((x + (width - text_width) / 2), (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);
|
||||||
|
isUseCutText ? outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(cutText.c_str()))
|
||||||
|
: outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||||
|
break;
|
||||||
|
case StellarX::ControlShape::CIRCLE://有边框填充圆形
|
||||||
|
fillcircle(x + width / 2, y + height / 2, min(width, height) / 2);
|
||||||
|
isUseCutText ? outtextxy(x + width / 2 - text_width / 2, y + height / 2 - text_height / 2, LPCTSTR(cutText.c_str()))
|
||||||
|
: outtextxy(x + width / 2 - text_width / 2, y + height / 2 - text_height / 2, LPCTSTR(text.c_str()));
|
||||||
|
break;
|
||||||
|
case StellarX::ControlShape::B_CIRCLE://无边框填充圆形
|
||||||
|
solidcircle(x + width / 2, y + height / 2, min(width, height) / 2);
|
||||||
|
isUseCutText ? outtextxy(x + width / 2 - text_width / 2, y + height / 2 - text_height / 2, LPCTSTR(cutText.c_str()))
|
||||||
|
: outtextxy(x + width / 2 - text_width / 2, y + height / 2 - text_height / 2, LPCTSTR(text.c_str()));
|
||||||
|
break;
|
||||||
|
case StellarX::ControlShape::ELLIPSE://有边框填充椭圆
|
||||||
|
fillellipse(x, y, x + width, y + height);
|
||||||
|
isUseCutText ? outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(cutText.c_str()))
|
||||||
|
: outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||||
|
break;
|
||||||
|
case StellarX::ControlShape::B_ELLIPSE://无边框填充椭圆
|
||||||
|
solidellipse(x, y, x + width, y + height);
|
||||||
|
isUseCutText ? outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(cutText.c_str()))
|
||||||
|
: outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreStyle();//恢复默认字体样式和颜色
|
||||||
|
dirty = false; //标记按钮不需要重绘
|
||||||
|
|
||||||
}
|
}
|
||||||
// 处理鼠标事件,检测点击和悬停状态
|
// 处理鼠标事件,检测点击和悬停状态
|
||||||
// 根据按钮模式和形状进行不同的处理
|
// 根据按钮模式和形状进行不同的处理
|
||||||
void Button::handleEvent(const ExMessage& msg)
|
bool Button::handleEvent(const ExMessage& msg)
|
||||||
{
|
{
|
||||||
|
if (!show)
|
||||||
|
return false;
|
||||||
|
|
||||||
bool oldHover = hover;
|
bool oldHover = hover;
|
||||||
bool oldClick = click;
|
bool oldClick = click;
|
||||||
|
bool consume = false;//是否消耗事件
|
||||||
|
// 记录鼠标位置(用于tip定位)
|
||||||
|
if (msg.message == WM_MOUSEMOVE)
|
||||||
|
{
|
||||||
|
lastMouseX = msg.x;
|
||||||
|
lastMouseY = msg.y;
|
||||||
|
}
|
||||||
// 检测悬停状态(根据不同形状)
|
// 检测悬停状态(根据不同形状)
|
||||||
switch (shape)
|
switch (shape)
|
||||||
{
|
{
|
||||||
@@ -159,25 +314,31 @@ void Button::handleEvent(const ExMessage& msg)
|
|||||||
// 处理鼠标点击事件
|
// 处理鼠标点击事件
|
||||||
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;
|
||||||
dirty = true;
|
dirty = true;
|
||||||
|
consume = true;
|
||||||
}
|
}
|
||||||
else if (mode == StellarX::ButtonMode::TOGGLE)
|
else if (mode == StellarX::ButtonMode::TOGGLE)
|
||||||
{
|
{
|
||||||
// TOGGLE模式在鼠标释放时处理
|
// TOGGLE模式在鼠标释放时处理
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 处理鼠标释放事件
|
// NORMAL 模式:鼠标在按钮上释放时才触发点击回调,如果移出区域则取消点击状态。
|
||||||
|
// TOGGLE 模式:在释放时切换状态,并触发相应的开/关回调。
|
||||||
else if (msg.message == WM_LBUTTONUP && hover && mode != StellarX::ButtonMode::DISABLED)
|
else if (msg.message == WM_LBUTTONUP && hover && mode != StellarX::ButtonMode::DISABLED)
|
||||||
{
|
{
|
||||||
|
hideTooltip(); // 隐藏悬停提示
|
||||||
if (mode == StellarX::ButtonMode::NORMAL && click)
|
if (mode == StellarX::ButtonMode::NORMAL && click)
|
||||||
{
|
{
|
||||||
if (onClickCallback) onClickCallback();
|
if (onClickCallback) onClickCallback();
|
||||||
click = false;
|
click = false;
|
||||||
dirty = true;
|
dirty = true;
|
||||||
// 使用新的flushmessage函数刷新消息队列:cite[2]:cite[3]
|
consume = true;
|
||||||
|
hideTooltip();
|
||||||
|
// 清除消息队列中积压的鼠标和键盘消息,防止本次点击事件被重复处理
|
||||||
flushmessage(EX_MOUSE | EX_KEY);
|
flushmessage(EX_MOUSE | EX_KEY);
|
||||||
}
|
}
|
||||||
else if (mode == StellarX::ButtonMode::TOGGLE)
|
else if (mode == StellarX::ButtonMode::TOGGLE)
|
||||||
@@ -186,11 +347,15 @@ void Button::handleEvent(const ExMessage& msg)
|
|||||||
if (click && onToggleOnCallback) onToggleOnCallback();
|
if (click && onToggleOnCallback) onToggleOnCallback();
|
||||||
else if (!click && onToggleOffCallback) onToggleOffCallback();
|
else if (!click && onToggleOffCallback) onToggleOffCallback();
|
||||||
dirty = true;
|
dirty = true;
|
||||||
// 使用新的flushmessage函数刷新消息队列:cite[2]:cite[3]
|
consume = true;
|
||||||
|
refreshTooltipTextForState();
|
||||||
|
hideTooltip();
|
||||||
|
// 清除消息队列中积压的鼠标和键盘消息,防止本次点击事件被重复处理
|
||||||
flushmessage(EX_MOUSE | EX_KEY);
|
flushmessage(EX_MOUSE | EX_KEY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 处理鼠标移出区域的情况
|
// 处理鼠标移出区域的情况
|
||||||
|
|
||||||
else if (msg.message == WM_MOUSEMOVE)
|
else if (msg.message == WM_MOUSEMOVE)
|
||||||
{
|
{
|
||||||
if (!hover && mode == StellarX::ButtonMode::NORMAL && click)
|
if (!hover && mode == StellarX::ButtonMode::NORMAL && click)
|
||||||
@@ -199,32 +364,74 @@ void Button::handleEvent(const ExMessage& msg)
|
|||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
else if (hover != oldHover)
|
else if (hover != oldHover)
|
||||||
{
|
|
||||||
dirty = true;
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tipEnabled)
|
||||||
|
{
|
||||||
|
if (hover && !oldHover)
|
||||||
|
{
|
||||||
|
// 刚刚进入悬停:开计时,暂不显示
|
||||||
|
tipHoverTick = GetTickCount64();
|
||||||
|
tipVisible = false;
|
||||||
|
}
|
||||||
|
if (!hover && oldHover)
|
||||||
|
{
|
||||||
|
// 刚移出:立即隐藏
|
||||||
|
hideTooltip();
|
||||||
|
}
|
||||||
|
if (hover && !tipVisible)
|
||||||
|
{
|
||||||
|
// 到点就显示
|
||||||
|
if (GetTickCount64() - tipHoverTick >= (ULONGLONG)tipDelayMs)
|
||||||
|
{
|
||||||
|
tipVisible = true;
|
||||||
|
|
||||||
|
// 定位(跟随鼠标 or 相对按钮)
|
||||||
|
int tipX = tipFollowCursor ? (lastMouseX + tipOffsetX) : lastMouseX;
|
||||||
|
int tipY = tipFollowCursor ? (lastMouseY + tipOffsetY) : y + height;
|
||||||
|
// 设置文本(用户可能动态改了提示文本
|
||||||
|
if (tipUserOverride)
|
||||||
|
{
|
||||||
|
if (mode == StellarX::ButtonMode::NORMAL)
|
||||||
|
tipLabel.setText(tipTextClick);
|
||||||
|
else if (mode == StellarX::ButtonMode::TOGGLE)
|
||||||
|
tipLabel.setText(click ? tipTextOn : tipTextOff);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (mode == StellarX::ButtonMode::TOGGLE)
|
||||||
|
tipLabel.setText(click ? tipTextOn : tipTextOff);
|
||||||
|
// 设置位置
|
||||||
|
tipLabel.setX(tipX);
|
||||||
|
tipLabel.setY(tipY);
|
||||||
|
// 标记需要绘制
|
||||||
|
tipLabel.setDirty(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果状态发生变化,标记需要重绘
|
// 如果状态发生变化,标记需要重绘
|
||||||
if (hover != oldHover || click != oldClick)
|
if (hover != oldHover || click != oldClick)
|
||||||
{
|
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
|
||||||
|
|
||||||
// 如果需要重绘,立即执行
|
// 如果需要重绘,立即执行
|
||||||
if (dirty)
|
if (dirty)
|
||||||
{
|
requestRepaint(parent);
|
||||||
draw();
|
|
||||||
}
|
if (tipEnabled && tipVisible)
|
||||||
|
tipLabel.draw();
|
||||||
|
|
||||||
|
return consume;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Button::setOnClickListener(const std::function<void()>&& callback)
|
void Button::setOnClickListener(const std::function<void()>&& callback)
|
||||||
{
|
{
|
||||||
this->onClickCallback = callback;
|
this->onClickCallback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Button::setOnToggleOnListener(const std::function<void()>&& callback)
|
void Button::setOnToggleOnListener(const std::function<void()>&& callback)
|
||||||
{
|
{
|
||||||
this->onToggleOnCallback = callback;
|
this->onToggleOnCallback = callback;
|
||||||
}
|
}
|
||||||
void Button::setOnToggleOffListener(const std::function<void()>&& callback)
|
void Button::setOnToggleOffListener(const std::function<void()>&& callback)
|
||||||
{
|
{
|
||||||
@@ -233,52 +440,77 @@ void Button::setOnToggleOffListener(const std::function<void()>&& callback)
|
|||||||
|
|
||||||
void Button::setbuttonMode(StellarX::ButtonMode mode)
|
void Button::setbuttonMode(StellarX::ButtonMode mode)
|
||||||
{
|
{
|
||||||
|
if (this->mode == StellarX::ButtonMode::DISABLED && mode != StellarX::ButtonMode::DISABLED)
|
||||||
|
textStyle.bStrikeOut = false;
|
||||||
//取值范围参考 buttMode的枚举注释
|
//取值范围参考 buttMode的枚举注释
|
||||||
this->mode = mode;
|
this->mode = mode;
|
||||||
|
dirty = true; // 标记需要重绘
|
||||||
}
|
}
|
||||||
|
|
||||||
int Button::setROUND_RECTANGLEwidth(int width)
|
void Button::setROUND_RECTANGLEwidth(int width)
|
||||||
{
|
{
|
||||||
return rouRectangleSize.ROUND_RECTANGLEwidth = width;
|
rouRectangleSize.ROUND_RECTANGLEwidth = width;
|
||||||
|
this->dirty = true; // 标记需要重绘
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int Button::setROUND_RECTANGLEheight(int height)
|
void Button::setROUND_RECTANGLEheight(int height)
|
||||||
{
|
{
|
||||||
return rouRectangleSize.ROUND_RECTANGLEheight = height;
|
rouRectangleSize.ROUND_RECTANGLEheight = height;
|
||||||
|
this->dirty = true; // 标记需要重绘
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Button::isClicked() const
|
bool Button::isClicked() const
|
||||||
{
|
{
|
||||||
return this->click;
|
return this->click;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Button::setFillMode(int mode)
|
void Button::setFillMode(StellarX::FillMode mode)
|
||||||
{
|
{
|
||||||
buttonFillMode = mode;
|
this->buttonFillMode = mode;
|
||||||
|
this->dirty = true; // 标记需要重绘
|
||||||
}
|
}
|
||||||
|
|
||||||
void Button::setFillIma(StellarX::FillStyle ima)
|
void Button::setFillIma(StellarX::FillStyle ima)
|
||||||
{
|
{
|
||||||
buttonFillIma = ima;
|
buttonFillIma = ima;
|
||||||
|
this->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Button::setFillIma(std::string imaNAme)
|
void Button::setFillIma(std::string imaNAme)
|
||||||
{
|
{
|
||||||
buttonFileIMAGE = new IMAGE;
|
if (buttonFileIMAGE)
|
||||||
loadimage(buttonFileIMAGE, imaNAme.c_str(),width-x,height-y);
|
{
|
||||||
|
delete buttonFileIMAGE;
|
||||||
|
buttonFileIMAGE = nullptr;
|
||||||
|
}
|
||||||
|
buttonFileIMAGE = new IMAGE;
|
||||||
|
loadimage(buttonFileIMAGE, imaNAme.c_str(), width, height);
|
||||||
|
this->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Button::setButtonBorder(COLORREF Border)
|
void Button::setButtonBorder(COLORREF Border)
|
||||||
{
|
{
|
||||||
buttonBorderColor = Border;
|
buttonBorderColor = Border;
|
||||||
|
this->dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Button::setButtonFalseColor(COLORREF color)
|
||||||
|
{
|
||||||
|
this->buttonFalseColor = color;
|
||||||
|
this->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Button::setButtonText(const char* text)
|
void Button::setButtonText(const char* text)
|
||||||
{
|
{
|
||||||
this->text = std::string(text);
|
this->text = std::string(text);
|
||||||
this->text_width = textwidth(LPCTSTR(this->text.c_str()));
|
this->text_width = textwidth(LPCTSTR(this->text.c_str()));
|
||||||
this->text_height = textheight(LPCTSTR(this->text.c_str()));
|
this->text_height = textheight(LPCTSTR(this->text.c_str()));
|
||||||
|
this->dirty = true;
|
||||||
|
this->needCutText = true;
|
||||||
|
if (!tipUserOverride)
|
||||||
|
tipTextClick = tipTextOn = tipTextOff = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Button::setButtonText(std::string text)
|
void Button::setButtonText(std::string text)
|
||||||
@@ -287,89 +519,178 @@ void Button::setButtonText(std::string text)
|
|||||||
this->text_width = textwidth(LPCTSTR(this->text.c_str()));
|
this->text_width = textwidth(LPCTSTR(this->text.c_str()));
|
||||||
this->text_height = textheight(LPCTSTR(this->text.c_str()));
|
this->text_height = textheight(LPCTSTR(this->text.c_str()));
|
||||||
this->dirty = true; // 标记需要重绘
|
this->dirty = true; // 标记需要重绘
|
||||||
|
this->needCutText = true;
|
||||||
|
if (!tipUserOverride)
|
||||||
|
tipTextClick = tipTextOn = tipTextOff = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Button::setButtonShape(StellarX::ControlShape shape)
|
void Button::setButtonShape(StellarX::ControlShape shape)
|
||||||
{
|
{
|
||||||
this->shape = shape;
|
this->shape = shape;
|
||||||
|
this->dirty = true;
|
||||||
|
this->needCutText = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//允许通过外部函数修改按钮的点击状态,并执行相应的回调函数
|
||||||
|
void Button::setButtonClick(BOOL click)
|
||||||
|
{
|
||||||
|
this->click = click;
|
||||||
|
|
||||||
|
if (mode == StellarX::ButtonMode::NORMAL && click)
|
||||||
|
{
|
||||||
|
if (onClickCallback) onClickCallback();
|
||||||
|
dirty = true;
|
||||||
|
hideTooltip();
|
||||||
|
// 清除消息队列中积压的鼠标和键盘消息,防止本次点击事件被重复处理
|
||||||
|
flushmessage(EX_MOUSE | EX_KEY);
|
||||||
|
}
|
||||||
|
else if (mode == StellarX::ButtonMode::TOGGLE)
|
||||||
|
{
|
||||||
|
if (click && onToggleOnCallback) onToggleOnCallback();
|
||||||
|
else if (!click && onToggleOffCallback) onToggleOffCallback();
|
||||||
|
dirty = true;
|
||||||
|
refreshTooltipTextForState();
|
||||||
|
hideTooltip();
|
||||||
|
// 清除消息队列中积压的鼠标和键盘消息,防止本次点击事件被重复处理
|
||||||
|
flushmessage(EX_MOUSE | EX_KEY);
|
||||||
|
}
|
||||||
|
if (dirty)
|
||||||
|
requestRepaint(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string Button::getButtonText() const
|
std::string Button::getButtonText() const
|
||||||
{
|
{
|
||||||
return this->text;
|
return this->text;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* Button::getButtonText_c() const
|
const char* Button::getButtonText_c() const
|
||||||
{
|
{
|
||||||
return this->text.c_str();
|
return this->text.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
StellarX::ButtonMode Button::getButtonMode() const
|
StellarX::ButtonMode Button::getButtonMode() const
|
||||||
{
|
{
|
||||||
return this->mode;
|
return this->mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
StellarX::ControlShape Button::getButtonShape() const
|
StellarX::ControlShape Button::getButtonShape() const
|
||||||
{
|
{
|
||||||
return this->shape;
|
return this->shape;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Button::getFillMode() const
|
StellarX::FillMode Button::getFillMode() const
|
||||||
{
|
{
|
||||||
return this->buttonFillMode;
|
return this->buttonFillMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
StellarX::FillStyle Button::getFillIma() const
|
StellarX::FillStyle Button::getFillIma() const
|
||||||
{
|
{
|
||||||
return this->buttonFillIma;
|
return this->buttonFillIma;
|
||||||
}
|
}
|
||||||
|
|
||||||
IMAGE* Button::getFillImaImage() const
|
IMAGE* Button::getFillImaImage() const
|
||||||
{
|
{
|
||||||
return this->buttonFileIMAGE;
|
return this->buttonFileIMAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
COLORREF Button::getButtonBorder() const
|
COLORREF Button::getButtonBorder() const
|
||||||
{
|
{
|
||||||
return this->buttonBorderColor;
|
return this->buttonBorderColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
COLORREF Button::getButtonTextColor() const
|
COLORREF Button::getButtonTextColor() const
|
||||||
{
|
{
|
||||||
return this->textStyle.color;
|
return this->textStyle.color;
|
||||||
}
|
}
|
||||||
|
|
||||||
StellarX::ControlText Button::getButtonTextStyle() const
|
StellarX::ControlText Button::getButtonTextStyle() const
|
||||||
{
|
{
|
||||||
return this->textStyle;
|
return this->textStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Button::getButtonWidth() const
|
||||||
|
{
|
||||||
|
return this->width;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Button::getButtonHeight() const
|
||||||
|
{
|
||||||
|
return this->height;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool Button::isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius)
|
bool Button::isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius)
|
||||||
{
|
{
|
||||||
double dis = sqrt(pow(mouseX - x, 2) + pow(mouseY - y, 2));
|
double dis = sqrt(pow(mouseX - x, 2) + pow(mouseY - y, 2));
|
||||||
if (dis <= radius)
|
if (dis <= radius)
|
||||||
return true;
|
return true;
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Button::isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, int height)
|
bool Button::isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, int height)
|
||||||
{
|
{
|
||||||
int centerX = (x + width) / 2;
|
int centerX = (x + width) / 2;
|
||||||
int centerY = (y + height) / 2;
|
int centerY = (y + height) / 2;
|
||||||
int majorAxis = (width - x) / 2;
|
int majorAxis = (width - x) / 2;
|
||||||
int minorAxis = (height - y) / 2;
|
int minorAxis = (height - y) / 2;
|
||||||
double dx = mouseX - centerX;
|
double dx = mouseX - centerX;
|
||||||
double dy = mouseY - centerY;
|
double dy = mouseY - centerY;
|
||||||
double normalizedDistance = (dx * dx) / (majorAxis * majorAxis) + (dy * dy) / (minorAxis * minorAxis);
|
double normalizedDistance = (dx * dx) / (majorAxis * majorAxis) + (dy * dy) / (minorAxis * minorAxis);
|
||||||
|
|
||||||
// 判断鼠标是否在椭圆内
|
// 判断鼠标是否在椭圆内
|
||||||
if (normalizedDistance <= 1.0)
|
if (normalizedDistance <= 1.0)
|
||||||
return true;
|
return true;
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Button::cutButtonText()
|
||||||
|
{
|
||||||
|
const int contentW = 1 > this->width - 2 * padX ? 1 : this->width - 2 * padX;
|
||||||
|
// 放得下:不截断,直接用原文
|
||||||
|
if (textwidth(LPCTSTR(this->text.c_str())) <= contentW) {
|
||||||
|
isUseCutText = false;
|
||||||
|
needCutText = false;
|
||||||
|
cutText.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 放不下:按语言偏好裁切(ASCII→词边界;CJK→逐字符,不撕裂双字节)
|
||||||
|
if (is_ascii_only(this->text))
|
||||||
|
{
|
||||||
|
cutText = ellipsize_ascii_pref(this->text, contentW); // "..."
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cutText = ellipsize_cjk_pref(this->text, contentW, "…"); // 全角省略号
|
||||||
|
|
||||||
|
}
|
||||||
|
isUseCutText = true;
|
||||||
|
needCutText = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Button::hideTooltip()
|
||||||
|
{
|
||||||
|
if (tipVisible)
|
||||||
|
{
|
||||||
|
tipVisible = false;
|
||||||
|
tipLabel.hide(); // 还原快照+作废,防止残影
|
||||||
|
tipHoverTick = GetTickCount64(); // 重置计时基线
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Button::refreshTooltipTextForState()
|
||||||
|
{
|
||||||
|
if (tipUserOverride) return; // 用户显式设置过 tipText,保持不变
|
||||||
|
if (mode == StellarX::ButtonMode::NORMAL)
|
||||||
|
tipLabel.setText(tipTextClick);
|
||||||
|
else if (mode == StellarX::ButtonMode::TOGGLE)
|
||||||
|
tipLabel.setText(click ? tipTextOn : tipTextOff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
286
src/Canvas.cpp
286
src/Canvas.cpp
@@ -1,17 +1,59 @@
|
|||||||
#include "StellarX/Canvas.h"
|
#include "Canvas.h"
|
||||||
|
|
||||||
|
Canvas::Canvas()
|
||||||
|
:Control(0, 0, 100, 100)
|
||||||
|
{
|
||||||
|
this->id = "Canvas";
|
||||||
|
}
|
||||||
|
|
||||||
Canvas::Canvas(int x, int y, int width, int height)
|
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()
|
||||||
|
{
|
||||||
|
controls.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void Canvas::draw()
|
void Canvas::draw()
|
||||||
{
|
{
|
||||||
|
if (!dirty || !show)
|
||||||
|
{
|
||||||
|
for (auto& control : controls)
|
||||||
|
if (auto c = dynamic_cast<Table*>(control.get()))
|
||||||
|
c->draw();
|
||||||
|
return;
|
||||||
|
}
|
||||||
saveStyle();
|
saveStyle();
|
||||||
|
|
||||||
setlinecolor(canvasBorderClor);//设置线色
|
setlinecolor(canvasBorderClor);//设置线色
|
||||||
setfillcolor(canvasBkClor);//设置填充色
|
if(StellarX::FillMode::Null != canvasFillMode)
|
||||||
|
setfillcolor(canvasBkClor);//设置填充色
|
||||||
setfillstyle((int)canvasFillMode);//设置填充模式
|
setfillstyle((int)canvasFillMode);//设置填充模式
|
||||||
setlinestyle((int)canvasLineStyle, canvaslinewidth);
|
setlinestyle((int)canvasLineStyle, canvaslinewidth);
|
||||||
|
|
||||||
|
// 在绘制画布之前,先恢复并更新背景快照:
|
||||||
|
// 1. 如果已有快照,则先回贴旧快照以清除之前的内容。
|
||||||
|
// 2. 当坐标或尺寸变化,或缓存图像无效时,丢弃旧快照并重新抓取新的背景。
|
||||||
|
if (hasSnap)
|
||||||
|
{
|
||||||
|
// 恢复旧快照,清除上一次绘制
|
||||||
|
restBackground();
|
||||||
|
// 如果位置或尺寸变了,或没有有效缓存,则重新抓取
|
||||||
|
if (!saveBkImage || saveBkX != this->x || saveBkY != this->y || saveWidth != this->width || saveHeight != this->height)
|
||||||
|
{
|
||||||
|
discardBackground();
|
||||||
|
saveBackground(this->x, this->y, this->width, this->height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 首次绘制或没有快照时直接抓取背景
|
||||||
|
saveBackground(this->x, this->y, this->width, this->height);
|
||||||
|
}
|
||||||
|
// 再次恢复最新快照,确保绘制区域干净
|
||||||
|
restBackground();
|
||||||
//根据画布形状绘制
|
//根据画布形状绘制
|
||||||
switch (shape)
|
switch (shape)
|
||||||
{
|
{
|
||||||
@@ -30,22 +72,38 @@ void Canvas::draw()
|
|||||||
}
|
}
|
||||||
// 绘制所有子控件
|
// 绘制所有子控件
|
||||||
for (auto& control : controls)
|
for (auto& control : controls)
|
||||||
|
{
|
||||||
|
control->setDirty(true);
|
||||||
control->draw();
|
control->draw();
|
||||||
|
}
|
||||||
|
|
||||||
restoreStyle();
|
restoreStyle();
|
||||||
|
dirty = false; //标记画布不需要重绘
|
||||||
}
|
}
|
||||||
|
|
||||||
void Canvas::handleEvent(const ExMessage& msg)
|
bool Canvas::handleEvent(const ExMessage& msg)
|
||||||
{
|
{
|
||||||
for (auto& control : controls) {
|
if (!show)return false;
|
||||||
control->handleEvent(msg);
|
bool consumed = false;
|
||||||
|
bool anyDirty = false;
|
||||||
|
|
||||||
|
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||||
|
{
|
||||||
|
consumed |= it->get()->handleEvent(msg);
|
||||||
|
if (it->get()->isDirty()) anyDirty = true;
|
||||||
}
|
}
|
||||||
|
if (anyDirty) requestRepaint(parent);
|
||||||
|
return consumed;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Canvas::addControl(std::unique_ptr<Control> control)
|
void Canvas::addControl(std::unique_ptr<Control> control)
|
||||||
{
|
{
|
||||||
|
//坐标转化
|
||||||
|
control->setX(control->getLocalX() + this->x);
|
||||||
|
control->setY(control->getLocalY() + this->y);
|
||||||
|
control->setParent(this);
|
||||||
controls.push_back(std::move(control));
|
controls.push_back(std::move(control));
|
||||||
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Canvas::setShape(StellarX::ControlShape shape)
|
void Canvas::setShape(StellarX::ControlShape shape)
|
||||||
@@ -57,12 +115,14 @@ void Canvas::setShape(StellarX::ControlShape shape)
|
|||||||
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;
|
||||||
|
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;
|
||||||
|
dirty = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,26 +130,236 @@ void Canvas::setShape(StellarX::ControlShape shape)
|
|||||||
void Canvas::setCanvasfillMode(StellarX::FillMode mode)
|
void Canvas::setCanvasfillMode(StellarX::FillMode mode)
|
||||||
{
|
{
|
||||||
this->canvasFillMode = mode;
|
this->canvasFillMode = mode;
|
||||||
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Canvas::setBorderColor(COLORREF color)
|
void Canvas::setBorderColor(COLORREF color)
|
||||||
{
|
{
|
||||||
this->canvasBorderClor = color;
|
this->canvasBorderClor = color;
|
||||||
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Canvas::setCanvasBkColor(COLORREF color)
|
void Canvas::setCanvasBkColor(COLORREF color)
|
||||||
{
|
{
|
||||||
this->canvasBkClor = color;
|
this->canvasBkClor = color;
|
||||||
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Canvas::setCanvasLineStyle(StellarX::LineStyle style)
|
void Canvas::setCanvasLineStyle(StellarX::LineStyle style)
|
||||||
{
|
{
|
||||||
this->canvasLineStyle = style;
|
this->canvasLineStyle = style;
|
||||||
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Canvas::setLinewidth(int width)
|
void Canvas::setLinewidth(int width)
|
||||||
{
|
{
|
||||||
this->canvaslinewidth = width;
|
this->canvaslinewidth = 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();
|
||||||
|
|
||||||
|
// 记录父容器原始尺寸(用于计算子控件的右/下边距)
|
||||||
|
int origParentW = this->localWidth;
|
||||||
|
int origParentH = this->localHeight;
|
||||||
|
|
||||||
|
// 当前容器的新尺寸
|
||||||
|
int finalW = this->width;
|
||||||
|
int finalH = this->height;
|
||||||
|
|
||||||
|
// 当前容器的新坐标(全局坐标)
|
||||||
|
int parentX = this->x;
|
||||||
|
int parentY = this->y;
|
||||||
|
|
||||||
|
// 调整每个子控件在 AnchorToEdges 模式下的位置与尺寸
|
||||||
|
for (auto& ch : controls)
|
||||||
|
{
|
||||||
|
// Only adjust when using anchor-to-edges layout
|
||||||
|
if (ch->getLayoutMode() == StellarX::LayoutMode::AnchorToEdges)
|
||||||
|
{
|
||||||
|
// Determine whether this child is a Table; tables keep their height constant
|
||||||
|
bool isTable = (dynamic_cast<Table*>(ch.get()) != nullptr);
|
||||||
|
|
||||||
|
// Unpack anchors
|
||||||
|
auto a1 = ch->getAnchor_1();
|
||||||
|
auto a2 = ch->getAnchor_2();
|
||||||
|
|
||||||
|
bool anchorLeft = (a1 == StellarX::Anchor::Left || a2 == StellarX::Anchor::Left);
|
||||||
|
bool anchorRight = (a1 == StellarX::Anchor::Right || a2 == StellarX::Anchor::Right);
|
||||||
|
bool anchorTop = (a1 == StellarX::Anchor::Top || a2 == StellarX::Anchor::Top);
|
||||||
|
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 (isTable)
|
||||||
|
{
|
||||||
|
anchorLeft = true;
|
||||||
|
anchorRight = true;
|
||||||
|
// If no explicit vertical anchor was provided, default to top.
|
||||||
|
if (!(anchorTop || anchorBottom))
|
||||||
|
{
|
||||||
|
anchorTop = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute new X and width
|
||||||
|
int newX = ch->getX();
|
||||||
|
int newWidth = ch->getWidth();
|
||||||
|
if (anchorLeft && anchorRight)
|
||||||
|
{
|
||||||
|
// Scale horizontally relative to parent's size.
|
||||||
|
if (origParentW > 0)
|
||||||
|
{
|
||||||
|
// Maintain proportional position and size based on original local values.
|
||||||
|
double scaleW = static_cast<double>(finalW) / static_cast<double>(origParentW);
|
||||||
|
newX = parentX + static_cast<int>(ch->getLocalX() * scaleW + 0.5);
|
||||||
|
newWidth = static_cast<int>(ch->getLocalWidth() * scaleW + 0.5);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Fallback: keep original
|
||||||
|
newX = parentX + ch->getLocalX();
|
||||||
|
newWidth = ch->getLocalWidth();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (anchorLeft && !anchorRight)
|
||||||
|
{
|
||||||
|
// Only left anchored: keep original width and left margin.
|
||||||
|
newWidth = ch->getLocalWidth();
|
||||||
|
newX = parentX + ch->getLocalX();
|
||||||
|
}
|
||||||
|
else if (!anchorLeft && anchorRight)
|
||||||
|
{
|
||||||
|
// Only right anchored: keep original width and right margin.
|
||||||
|
newWidth = ch->getLocalWidth();
|
||||||
|
int origRightDist = origParentW - (ch->getLocalX() + ch->getLocalWidth());
|
||||||
|
newX = parentX + finalW - origRightDist - newWidth;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No horizontal anchor: position relative to parent's left and width unchanged.
|
||||||
|
newWidth = ch->getLocalWidth();
|
||||||
|
newX = parentX + ch->getLocalX();
|
||||||
|
}
|
||||||
|
ch->setX(newX);
|
||||||
|
ch->setWidth(newWidth);
|
||||||
|
|
||||||
|
// Compute new Y and height
|
||||||
|
int newY = ch->getY();
|
||||||
|
int newHeight = ch->getHeight();
|
||||||
|
if (isTable)
|
||||||
|
{
|
||||||
|
// Table: Height remains constant; adjust Y based on anchors.
|
||||||
|
newHeight = ch->getLocalHeight();
|
||||||
|
if (anchorTop && anchorBottom)
|
||||||
|
{
|
||||||
|
// If both top and bottom anchored, scale Y but keep height.
|
||||||
|
if (origParentH > 0)
|
||||||
|
{
|
||||||
|
double scaleH = static_cast<double>(finalH) / static_cast<double>(origParentH);
|
||||||
|
newY = parentY + static_cast<int>(ch->getLocalY() * scaleH + 0.5);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newY = parentY + ch->getLocalY();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (anchorTop && !anchorBottom)
|
||||||
|
{
|
||||||
|
// Top anchored only
|
||||||
|
newY = parentY + ch->getLocalY();
|
||||||
|
}
|
||||||
|
else if (!anchorTop && anchorBottom)
|
||||||
|
{
|
||||||
|
// Bottom anchored only
|
||||||
|
int origBottomDist = origParentH - (ch->getLocalY() + ch->getLocalHeight());
|
||||||
|
newY = parentY + finalH - origBottomDist - newHeight;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No vertical anchor: default to top
|
||||||
|
newY = parentY + ch->getLocalY();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (anchorTop && anchorBottom)
|
||||||
|
{
|
||||||
|
// Scale vertically relative to parent's size.
|
||||||
|
if (origParentH > 0)
|
||||||
|
{
|
||||||
|
double scaleH = static_cast<double>(finalH) / static_cast<double>(origParentH);
|
||||||
|
newY = parentY + static_cast<int>(ch->getLocalY() * scaleH + 0.5);
|
||||||
|
newHeight = static_cast<int>(ch->getLocalHeight() * scaleH + 0.5);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newY = parentY + ch->getLocalY();
|
||||||
|
newHeight = ch->getLocalHeight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (anchorTop && !anchorBottom)
|
||||||
|
{
|
||||||
|
// Top anchored only: keep height constant
|
||||||
|
newHeight = ch->getLocalHeight();
|
||||||
|
newY = parentY + ch->getLocalY();
|
||||||
|
}
|
||||||
|
else if (!anchorTop && anchorBottom)
|
||||||
|
{
|
||||||
|
// Bottom anchored only: keep height and adjust Y relative to bottom
|
||||||
|
newHeight = ch->getLocalHeight();
|
||||||
|
int origBottomDist = origParentH - (ch->getLocalY() + ch->getLocalHeight());
|
||||||
|
newY = parentY + finalH - origBottomDist - newHeight;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No vertical anchor: position relative to parent's top, height constant.
|
||||||
|
newHeight = ch->getLocalHeight();
|
||||||
|
newY = parentY + ch->getLocalY();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ch->setY(newY);
|
||||||
|
ch->setHeight(newHeight);
|
||||||
|
}
|
||||||
|
// Always forward the window resize event to the child (recursively).
|
||||||
|
ch->onWindowResize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Canvas::requestRepaint(Control* parent)
|
||||||
|
{
|
||||||
|
if (this == parent)
|
||||||
|
{
|
||||||
|
for (auto& control : controls)
|
||||||
|
if (control->isDirty() && control->IsVisible())
|
||||||
|
control->draw();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
onRequestRepaintAsRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
111
src/Control.cpp
111
src/Control.cpp
@@ -1,4 +1,5 @@
|
|||||||
#include "StellarX/Control.h"
|
#include "Control.h"
|
||||||
|
#include<assert.h>
|
||||||
|
|
||||||
StellarX::ControlText& StellarX::ControlText::operator=(const ControlText& text)
|
StellarX::ControlText& StellarX::ControlText::operator=(const ControlText& text)
|
||||||
{
|
{
|
||||||
@@ -41,24 +42,116 @@ bool StellarX::ControlText::operator!=(const ControlText& text)
|
|||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
void Control::setIsVisible(bool show)
|
||||||
|
{
|
||||||
|
if (!show)
|
||||||
|
this->updateBackground();
|
||||||
|
this->show = show;
|
||||||
|
}
|
||||||
|
void Control::onWindowResize()
|
||||||
|
{
|
||||||
|
// 自己:丢快照 + 标脏
|
||||||
|
discardBackground();
|
||||||
|
setDirty(true);
|
||||||
|
}
|
||||||
|
void Control::setLayoutMode(StellarX::LayoutMode layoutMode_)
|
||||||
|
{
|
||||||
|
this->layoutMode = layoutMode_;
|
||||||
|
}
|
||||||
|
void Control::steAnchor(StellarX::Anchor anchor_1, StellarX::Anchor anchor_2)
|
||||||
|
{
|
||||||
|
this->anchor_1 = anchor_1;
|
||||||
|
this->anchor_2 = anchor_2;
|
||||||
|
}
|
||||||
|
StellarX::Anchor Control::getAnchor_1() const
|
||||||
|
{
|
||||||
|
return this->anchor_1;
|
||||||
|
}
|
||||||
|
StellarX::Anchor Control::getAnchor_2() const
|
||||||
|
{
|
||||||
|
return this->anchor_2;
|
||||||
|
}
|
||||||
|
StellarX::LayoutMode Control::getLayoutMode() const
|
||||||
|
{
|
||||||
|
return this->layoutMode;
|
||||||
|
}
|
||||||
// 保存当前的绘图状态(字体、颜色、线型等)
|
// 保存当前的绘图状态(字体、颜色、线型等)
|
||||||
// 在控件绘制前调用,确保不会影响全局绘图状态
|
// 在控件绘制前调用,确保不会影响全局绘图状态
|
||||||
void Control::saveStyle()
|
void Control::saveStyle()
|
||||||
{
|
{
|
||||||
gettextstyle(¤tFont); // 获取当前字体样式
|
|
||||||
currentColor = gettextcolor(); // 获取当前字体颜色
|
gettextstyle(currentFont); // 获取当前字体样式
|
||||||
currentBorderColor = getlinecolor(); //保存当前边框颜色
|
*currentColor = gettextcolor(); // 获取当前字体颜色
|
||||||
|
*currentBorderColor = getlinecolor(); //保存当前边框颜色
|
||||||
getlinestyle(currentLineStyle); //保存当前线型
|
getlinestyle(currentLineStyle); //保存当前线型
|
||||||
currentBkColor = getfillcolor(); //保存当前填充色
|
*currentBkColor = getfillcolor(); //保存当前填充色
|
||||||
}
|
}
|
||||||
// 恢复之前保存的绘图状态
|
// 恢复之前保存的绘图状态
|
||||||
// 在控件绘制完成后调用,恢复全局绘图状态
|
// 在控件绘制完成后调用,恢复全局绘图状态
|
||||||
void Control::restoreStyle()
|
void Control::restoreStyle()
|
||||||
{
|
{
|
||||||
settextstyle(¤tFont); // 恢复默认字体样式
|
settextstyle(currentFont); // 恢复默认字体样式
|
||||||
settextcolor(currentColor); // 恢复默认字体颜色
|
settextcolor(*currentColor); // 恢复默认字体颜色
|
||||||
setfillcolor(currentBkColor);
|
setfillcolor(*currentBkColor);
|
||||||
setlinestyle(currentLineStyle);
|
setlinestyle(currentLineStyle);
|
||||||
setlinecolor(currentBorderColor);
|
setlinecolor(*currentBorderColor);
|
||||||
setfillstyle(BS_SOLID);//恢复填充
|
setfillstyle(BS_SOLID);//恢复填充
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Control::requestRepaint(Control* parent)
|
||||||
|
{
|
||||||
|
if (parent) parent->requestRepaint(parent); // 向上冒泡
|
||||||
|
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;
|
||||||
|
saveBkX = x; saveBkY = y; saveWidth = w; saveHeight = h;
|
||||||
|
if (saveBkImage)
|
||||||
|
{
|
||||||
|
//尺寸变了才重建,避免反复 new/delete
|
||||||
|
if (saveBkImage->getwidth() != w || saveBkImage->getheight() != h)
|
||||||
|
{
|
||||||
|
delete saveBkImage; saveBkImage = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!saveBkImage) saveBkImage = new IMAGE(w, h);
|
||||||
|
|
||||||
|
SetWorkingImage(nullptr); // ★抓屏幕
|
||||||
|
getimage(saveBkImage, x, y, w, h);
|
||||||
|
hasSnap = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Control::restBackground()
|
||||||
|
{
|
||||||
|
if (!hasSnap || !saveBkImage) return;
|
||||||
|
// 直接回贴屏幕(与抓取一致)
|
||||||
|
SetWorkingImage(nullptr);
|
||||||
|
putimage(saveBkX, saveBkY, saveBkImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Control::discardBackground()
|
||||||
|
{
|
||||||
|
if (saveBkImage)
|
||||||
|
{
|
||||||
|
delete saveBkImage;
|
||||||
|
saveBkImage = nullptr;
|
||||||
|
}
|
||||||
|
hasSnap = false; saveWidth = saveHeight = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Control::updateBackground()
|
||||||
|
{
|
||||||
|
restBackground();
|
||||||
|
discardBackground();
|
||||||
|
}
|
||||||
|
|||||||
755
src/Dialog.cpp
Normal file
755
src/Dialog.cpp
Normal file
@@ -0,0 +1,755 @@
|
|||||||
|
#include "Dialog.h"
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Dialog::~Dialog()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dialog::draw()
|
||||||
|
{
|
||||||
|
if(!show)
|
||||||
|
{
|
||||||
|
// 如果对话框不可见且需要清理,执行清理
|
||||||
|
if (pendingCleanup && !isCleaning)
|
||||||
|
{
|
||||||
|
performDelayedCleanup();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 如果需要初始化,则执行初始化
|
||||||
|
if (needsInitialization && show)
|
||||||
|
{
|
||||||
|
initDialogSize();
|
||||||
|
needsInitialization = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dirty && show)
|
||||||
|
{
|
||||||
|
// 保存当前绘图状态
|
||||||
|
saveStyle();
|
||||||
|
|
||||||
|
Canvas::setBorderColor(this->borderColor);
|
||||||
|
Canvas::setLinewidth(BorderWidth);
|
||||||
|
Canvas::setCanvasBkColor(this->backgroundColor);
|
||||||
|
Canvas::setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
|
||||||
|
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
|
||||||
|
saveBackground(this->x, this->y, this->width, this->height);
|
||||||
|
//设置所有控件为脏状态
|
||||||
|
/*for(auto& c :this->controls)
|
||||||
|
c->setDirty(true);*/
|
||||||
|
restBackground();
|
||||||
|
Canvas::draw();
|
||||||
|
|
||||||
|
//绘制消息文本
|
||||||
|
settextcolor(textStyle.color);
|
||||||
|
|
||||||
|
//设置字体样式
|
||||||
|
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
||||||
|
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
||||||
|
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
|
||||||
|
|
||||||
|
|
||||||
|
int ty = y + closeButtonHeight + titleToTextMargin; // 文本起始Y坐标
|
||||||
|
for (auto& line:lines)
|
||||||
|
{
|
||||||
|
int tx = this->x + ((this->width - textwidth(line.c_str())) / 2); // 文本起始X坐标
|
||||||
|
outtextxy(tx, ty, LPCTSTR(line.c_str()));
|
||||||
|
ty = ty + textheight(LPCTSTR(line.c_str())) + 5; // 每行文本高度加5像素间距
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复绘图状态
|
||||||
|
restoreStyle();
|
||||||
|
|
||||||
|
dirty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool Dialog::handleEvent(const ExMessage& msg)
|
||||||
|
{
|
||||||
|
bool consume = false;
|
||||||
|
if (!show)
|
||||||
|
{
|
||||||
|
if (pendingCleanup && !isCleaning)
|
||||||
|
{
|
||||||
|
performDelayedCleanup();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果正在清理或标记为待清理,则不处理事件
|
||||||
|
if (pendingCleanup || isCleaning)
|
||||||
|
return false;
|
||||||
|
// 模态对话框:点击对话框外部区域时,发出提示音(\a)并吞噬该事件,不允许操作背景内容。
|
||||||
|
if (modal && msg.message == WM_LBUTTONUP &&
|
||||||
|
(msg.x < x || msg.x > x + width || msg.y < y || msg.y > y + height))
|
||||||
|
{
|
||||||
|
std::cout << "\a" << std::endl;
|
||||||
|
// 模态对话框不允许点击外部区域
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将事件传递给子控件处理
|
||||||
|
if (!consume)
|
||||||
|
consume = Canvas::handleEvent(msg);
|
||||||
|
|
||||||
|
// 每次事件处理后检查是否需要执行延迟清理
|
||||||
|
if (pendingCleanup && !isCleaning)
|
||||||
|
performDelayedCleanup();
|
||||||
|
return consume;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dialog::SetTitle(const std::string& title)
|
||||||
|
{
|
||||||
|
this->titleText = title;
|
||||||
|
if (this->title)
|
||||||
|
{
|
||||||
|
this->title->setText(title);
|
||||||
|
}
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dialog::SetMessage(const std::string& message)
|
||||||
|
{
|
||||||
|
this->message = message;
|
||||||
|
splitMessageLines();
|
||||||
|
getTextSize();
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dialog::SetType(StellarX::MessageBoxType type)
|
||||||
|
{
|
||||||
|
this->type = type;
|
||||||
|
// 重新初始化按钮
|
||||||
|
initButtons();
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dialog::SetModal(bool modal)
|
||||||
|
{
|
||||||
|
this->modal = modal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Dialog::SetResult(StellarX::MessageBoxResult result)
|
||||||
|
{
|
||||||
|
this->result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
StellarX::MessageBoxResult Dialog::GetResult() const
|
||||||
|
{
|
||||||
|
return this->result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Dialog::model() const
|
||||||
|
{
|
||||||
|
return modal;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dialog::Show()
|
||||||
|
{
|
||||||
|
if (pendingCleanup)
|
||||||
|
performDelayedCleanup();
|
||||||
|
|
||||||
|
show = true;
|
||||||
|
dirty = true;
|
||||||
|
needsInitialization = true;
|
||||||
|
close = false;
|
||||||
|
shouldClose = false;
|
||||||
|
|
||||||
|
if (modal)
|
||||||
|
{
|
||||||
|
// 模态对话框需要阻塞当前线程直到对话框关闭
|
||||||
|
if (modal)
|
||||||
|
{
|
||||||
|
// 记录当前窗口客户区尺寸,供轮询对比
|
||||||
|
RECT rc0;
|
||||||
|
GetClientRect(hWnd.getHwnd(), &rc0);
|
||||||
|
int lastW = rc0.right - rc0.left;
|
||||||
|
int lastH = rc0.bottom - rc0.top;
|
||||||
|
|
||||||
|
while (show && !close)
|
||||||
|
{
|
||||||
|
// ① 轮询窗口尺寸(不依赖 WM_SIZE)
|
||||||
|
RECT rc;
|
||||||
|
GetClientRect(hWnd.getHwnd(), &rc);
|
||||||
|
const int cw = rc.right - rc.left;
|
||||||
|
const int ch = rc.bottom - rc.top;
|
||||||
|
|
||||||
|
if (cw != lastW || ch != lastH)
|
||||||
|
{
|
||||||
|
lastW = cw;
|
||||||
|
lastH = ch;
|
||||||
|
|
||||||
|
// 通知父窗口:有新尺寸 → 标记 needResizeDirty
|
||||||
|
hWnd.scheduleResizeFromModal(cw, ch);
|
||||||
|
|
||||||
|
// 立即统一收口:父窗重绘 背景+普通控件(不会画到这只模态)
|
||||||
|
hWnd.pumpResizeIfNeeded();
|
||||||
|
|
||||||
|
// 这只模态在新尺寸下重建布局 / 重抓背景 → 本帧要画自己
|
||||||
|
setInitialization(true);
|
||||||
|
setDirty(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ② 处理这只对话框的鼠标/键盘(沿用你原来 EX_MOUSE | EX_KEY)
|
||||||
|
ExMessage msg;
|
||||||
|
if (peekmessage(&msg, EX_MOUSE | EX_KEY))
|
||||||
|
{
|
||||||
|
handleEvent(msg);
|
||||||
|
if (shouldClose)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ③ 最后一笔:只画这只模态,保证永远在最上层
|
||||||
|
if (dirty)
|
||||||
|
{
|
||||||
|
BeginBatchDraw();
|
||||||
|
this->draw(); // 注意:不要 requestRepaint(parent),只画自己
|
||||||
|
EndBatchDraw();
|
||||||
|
dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sleep(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingCleanup && !isCleaning)
|
||||||
|
performDelayedCleanup();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 非模态仍由主循环托管
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模态对话框关闭后执行清理
|
||||||
|
if (pendingCleanup && !isCleaning)
|
||||||
|
performDelayedCleanup();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// 非模态对话框只需标记为可见,由主循环处理
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void Dialog::Close()
|
||||||
|
{
|
||||||
|
if (!show) return;
|
||||||
|
|
||||||
|
show = false;
|
||||||
|
close = true;
|
||||||
|
dirty = true;
|
||||||
|
pendingCleanup = true; // 只标记需要清理,不立即执行
|
||||||
|
|
||||||
|
|
||||||
|
// 工厂模式下非模态触发回调 返回结果
|
||||||
|
if (resultCallback&& !modal)
|
||||||
|
resultCallback(this->result);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dialog::setInitialization(bool init)
|
||||||
|
{
|
||||||
|
if (init)
|
||||||
|
{
|
||||||
|
initDialogSize();
|
||||||
|
saveBackground((x - BorderWidth), (y - BorderWidth), (width + 2 * BorderWidth), (height + 2 * BorderWidth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Dialog::initButtons()
|
||||||
|
{
|
||||||
|
controls.clear();
|
||||||
|
|
||||||
|
switch (this->type)
|
||||||
|
{
|
||||||
|
case StellarX::MessageBoxType::OK: // 只有确定按钮
|
||||||
|
{
|
||||||
|
auto okbutton = createDialogButton((this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
|
||||||
|
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
|
||||||
|
"确定"
|
||||||
|
);
|
||||||
|
okbutton->setOnClickListener([this]()
|
||||||
|
{
|
||||||
|
this->SetResult(StellarX::MessageBoxResult::OK);
|
||||||
|
this->Close(); });
|
||||||
|
|
||||||
|
okbutton->textStyle = this->textStyle;
|
||||||
|
|
||||||
|
this->addControl(std::move(okbutton));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case StellarX::MessageBoxType::OKCancel: // 确定和取消按钮
|
||||||
|
{
|
||||||
|
auto okButton = createDialogButton(
|
||||||
|
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
|
||||||
|
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
|
||||||
|
"确定"
|
||||||
|
);
|
||||||
|
okButton->setOnClickListener([this]()
|
||||||
|
{
|
||||||
|
this->SetResult(StellarX::MessageBoxResult::OK);
|
||||||
|
this->hWnd.dialogClose = true;
|
||||||
|
this->Close(); });
|
||||||
|
|
||||||
|
auto cancelButton = createDialogButton(
|
||||||
|
(okButton.get()->getX() + okButton.get()->getButtonWidth() + buttonMargin),
|
||||||
|
okButton.get()->getY(),
|
||||||
|
"取消"
|
||||||
|
);
|
||||||
|
cancelButton->setOnClickListener([this]()
|
||||||
|
{
|
||||||
|
this->SetResult(StellarX::MessageBoxResult::Cancel);
|
||||||
|
this->hWnd.dialogClose = true;
|
||||||
|
this->Close(); });
|
||||||
|
|
||||||
|
okButton->textStyle = this->textStyle;
|
||||||
|
cancelButton->textStyle = this->textStyle;
|
||||||
|
|
||||||
|
this->addControl(std::move(okButton));
|
||||||
|
this->addControl(std::move(cancelButton));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case StellarX::MessageBoxType::YesNo: // 是和否按钮
|
||||||
|
{
|
||||||
|
auto yesButton = createDialogButton(
|
||||||
|
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
|
||||||
|
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
|
||||||
|
"是"
|
||||||
|
);
|
||||||
|
yesButton->setOnClickListener([this]()
|
||||||
|
{
|
||||||
|
this->SetResult(StellarX::MessageBoxResult::Yes);
|
||||||
|
this->hWnd.dialogClose = true;
|
||||||
|
this->Close(); });
|
||||||
|
|
||||||
|
auto noButton = createDialogButton(
|
||||||
|
(yesButton.get()->getX() + yesButton.get()->getButtonWidth() + buttonMargin),
|
||||||
|
yesButton.get()->getY(),
|
||||||
|
"否"
|
||||||
|
);
|
||||||
|
noButton->setOnClickListener([this]()
|
||||||
|
{
|
||||||
|
this->SetResult(StellarX::MessageBoxResult::No);
|
||||||
|
this->hWnd.dialogClose = true;
|
||||||
|
this->Close(); });
|
||||||
|
|
||||||
|
yesButton->textStyle = this->textStyle;
|
||||||
|
noButton->textStyle = this->textStyle;
|
||||||
|
|
||||||
|
this->addControl(std::move(yesButton));
|
||||||
|
this->addControl(std::move(noButton));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case StellarX::MessageBoxType::YesNoCancel: // 是、否和取消按钮
|
||||||
|
{
|
||||||
|
auto yesButton = createDialogButton(
|
||||||
|
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
|
||||||
|
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
|
||||||
|
"是"
|
||||||
|
);
|
||||||
|
yesButton->setOnClickListener([this]()
|
||||||
|
{
|
||||||
|
this->SetResult(StellarX::MessageBoxResult::Yes);
|
||||||
|
this->hWnd.dialogClose = true;
|
||||||
|
this->Close(); });
|
||||||
|
|
||||||
|
auto noButton = createDialogButton(
|
||||||
|
yesButton.get()->getX() + yesButton.get()->getButtonWidth() + buttonMargin,
|
||||||
|
yesButton.get()->getY(),
|
||||||
|
"否"
|
||||||
|
);
|
||||||
|
noButton->setOnClickListener([this]()
|
||||||
|
{
|
||||||
|
this->SetResult(StellarX::MessageBoxResult::No);
|
||||||
|
this->hWnd.dialogClose = true;
|
||||||
|
this->Close(); });
|
||||||
|
|
||||||
|
auto cancelButton = createDialogButton(
|
||||||
|
noButton.get()->getX() + noButton.get()->getButtonWidth() + buttonMargin,
|
||||||
|
noButton.get()->getY(),
|
||||||
|
"取消"
|
||||||
|
);
|
||||||
|
cancelButton->setOnClickListener([this]()
|
||||||
|
{
|
||||||
|
this->SetResult(StellarX::MessageBoxResult::Cancel);
|
||||||
|
this->hWnd.dialogClose = true;
|
||||||
|
this->Close(); });
|
||||||
|
|
||||||
|
yesButton->textStyle = this->textStyle;
|
||||||
|
noButton->textStyle = this->textStyle;
|
||||||
|
cancelButton->textStyle = this->textStyle;
|
||||||
|
|
||||||
|
|
||||||
|
this->addControl(std::move(yesButton));
|
||||||
|
this->addControl(std::move(noButton));
|
||||||
|
this->addControl(std::move(cancelButton));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case StellarX::MessageBoxType::RetryCancel: // 重试和取消按钮
|
||||||
|
{
|
||||||
|
auto retryButton = createDialogButton(
|
||||||
|
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
|
||||||
|
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
|
||||||
|
"重试"
|
||||||
|
);
|
||||||
|
retryButton->setOnClickListener([this]()
|
||||||
|
{
|
||||||
|
this->SetResult(StellarX::MessageBoxResult::Retry);
|
||||||
|
this->hWnd.dialogClose = true;
|
||||||
|
this->Close(); });
|
||||||
|
|
||||||
|
auto cancelButton = createDialogButton(
|
||||||
|
retryButton.get()->getX() + retryButton.get()->getButtonWidth() + buttonMargin,
|
||||||
|
retryButton.get()->getY(),
|
||||||
|
"取消"
|
||||||
|
);
|
||||||
|
cancelButton->setOnClickListener([this]()
|
||||||
|
{
|
||||||
|
this->SetResult(StellarX::MessageBoxResult::Cancel);
|
||||||
|
this->hWnd.dialogClose = true;
|
||||||
|
this->Close(); });
|
||||||
|
|
||||||
|
retryButton->textStyle = this->textStyle;
|
||||||
|
cancelButton->textStyle = this->textStyle;
|
||||||
|
|
||||||
|
this->addControl(std::move(retryButton));
|
||||||
|
this->addControl(std::move(cancelButton));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case StellarX::MessageBoxType::AbortRetryIgnore: // 中止、重试和忽略按钮
|
||||||
|
{
|
||||||
|
auto abortButton = createDialogButton(
|
||||||
|
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin* (buttonNum-1))) / 2),
|
||||||
|
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
|
||||||
|
"中止"
|
||||||
|
);
|
||||||
|
abortButton->setOnClickListener([this]()
|
||||||
|
{
|
||||||
|
this->SetResult(StellarX::MessageBoxResult::Abort);
|
||||||
|
this->hWnd.dialogClose = true;
|
||||||
|
this->Close();
|
||||||
|
});
|
||||||
|
auto retryButton = createDialogButton(
|
||||||
|
abortButton.get()->getX() + abortButton.get()->getButtonWidth() + buttonMargin,
|
||||||
|
abortButton.get()->getY(),
|
||||||
|
"重试"
|
||||||
|
);
|
||||||
|
retryButton->setOnClickListener([this]()
|
||||||
|
{
|
||||||
|
this->SetResult(StellarX::MessageBoxResult::Retry);
|
||||||
|
this->hWnd.dialogClose = true;
|
||||||
|
this->Close();
|
||||||
|
});
|
||||||
|
auto ignoreButton = createDialogButton(
|
||||||
|
retryButton.get()->getX() + retryButton.get()->getButtonWidth() + buttonMargin,
|
||||||
|
retryButton.get()->getY(),
|
||||||
|
"忽略"
|
||||||
|
);
|
||||||
|
ignoreButton.get()->setOnClickListener([this]()
|
||||||
|
{
|
||||||
|
this->SetResult(StellarX::MessageBoxResult::Ignore);
|
||||||
|
this->hWnd.dialogClose = true;
|
||||||
|
this->Close();
|
||||||
|
});
|
||||||
|
|
||||||
|
abortButton->textStyle = this->textStyle;
|
||||||
|
retryButton->textStyle = this->textStyle;
|
||||||
|
ignoreButton->textStyle = this->textStyle;
|
||||||
|
|
||||||
|
this->addControl(std::move(abortButton));
|
||||||
|
this->addControl(std::move(retryButton));
|
||||||
|
this->addControl(std::move(ignoreButton));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dialog::initCloseButton()
|
||||||
|
{
|
||||||
|
//初始化关闭按钮
|
||||||
|
auto but = std::make_unique<Button>
|
||||||
|
(
|
||||||
|
(this->x + this->width - closeButtonWidth) - 3, (this->y+3), closeButtonWidth-1, closeButtonHeight,
|
||||||
|
"X", // 按钮文本
|
||||||
|
RGB(255, 0, 0), // 按钮被点击颜色
|
||||||
|
this->canvasBkClor, // 按钮背景颜色
|
||||||
|
RGB(255, 0, 0), // 按钮被悬停颜色
|
||||||
|
StellarX::ButtonMode::NORMAL,
|
||||||
|
StellarX::ControlShape::B_RECTANGLE
|
||||||
|
);
|
||||||
|
but.get()->setButtonFalseColor(this->backgroundColor);
|
||||||
|
but.get()->enableTooltip(false);
|
||||||
|
but->setOnClickListener([this]() {
|
||||||
|
this->SetResult(StellarX::MessageBoxResult::Cancel);
|
||||||
|
this->hWnd.dialogClose = true;
|
||||||
|
this->Close(); });
|
||||||
|
|
||||||
|
this->closeButton = but.get();
|
||||||
|
this->addControl(std::move(but));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dialog::initTitle()
|
||||||
|
{
|
||||||
|
this->title = std::make_unique<Label>(this->x+5,this->y+5,titleText,textStyle.color);
|
||||||
|
title->setTextdisap(true);
|
||||||
|
title->textStyle = this->textStyle;
|
||||||
|
|
||||||
|
this->addControl(std::move(title));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dialog::splitMessageLines()
|
||||||
|
{
|
||||||
|
lines.clear(); // 先清空现有的行
|
||||||
|
|
||||||
|
std::string currentLine;
|
||||||
|
for (size_t i = 0; i < message.length(); i++) {
|
||||||
|
// 处理 换行符 \r\n \n \r
|
||||||
|
if (i + 1 < message.length() && (message[i] == '\r' || message[i] == '\n')||(message[i] == '\r' && message[i+1] == '\n'))
|
||||||
|
{
|
||||||
|
if (!currentLine.empty()) {
|
||||||
|
lines.push_back(currentLine);
|
||||||
|
currentLine.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message[i] == '\r' && message[i + 1] == '\n')
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentLine += message[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加最后一行(如果有内容)
|
||||||
|
if (!currentLine.empty())
|
||||||
|
{
|
||||||
|
lines.push_back(currentLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果消息为空,至少添加一个空行
|
||||||
|
if (lines.empty())
|
||||||
|
{
|
||||||
|
lines.push_back("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dialog::getTextSize()
|
||||||
|
{
|
||||||
|
saveStyle();
|
||||||
|
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
||||||
|
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
||||||
|
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
|
||||||
|
for (auto& text : lines)
|
||||||
|
{
|
||||||
|
int w = textwidth(LPCTSTR(text.c_str()));
|
||||||
|
int h = textheight(LPCTSTR(text.c_str()));
|
||||||
|
if (this->textHeight < h)
|
||||||
|
this->textHeight = h;
|
||||||
|
if (this->textWidth < w)
|
||||||
|
this->textWidth = w;
|
||||||
|
}
|
||||||
|
restoreStyle();
|
||||||
|
}
|
||||||
|
// 计算逻辑:对话框宽度取【文本区域最大宽度】和【按钮区域总宽度】中的较大值。
|
||||||
|
// 对话框高度 = 标题栏 + 文本区 + 按钮区 + 各种间距。
|
||||||
|
void Dialog::initDialogSize()
|
||||||
|
{
|
||||||
|
splitMessageLines(); // 分割消息行
|
||||||
|
getTextSize(); // 获取文本最大尺寸
|
||||||
|
|
||||||
|
// 获取功能按钮数量
|
||||||
|
switch (this->type)
|
||||||
|
{
|
||||||
|
case StellarX::MessageBoxType::OK: // 只有确定按钮
|
||||||
|
buttonNum = 1;
|
||||||
|
break;
|
||||||
|
case StellarX::MessageBoxType::OKCancel: // 确定和取消按钮
|
||||||
|
case StellarX::MessageBoxType::YesNo: // 是和否按钮
|
||||||
|
case StellarX::MessageBoxType::RetryCancel: // 重试和取消按钮
|
||||||
|
buttonNum = 2;
|
||||||
|
break;
|
||||||
|
case StellarX::MessageBoxType::YesNoCancel: // 是、否和取消按钮
|
||||||
|
case StellarX::MessageBoxType::AbortRetryIgnore: // 中止、重试和忽略按钮
|
||||||
|
buttonNum = 3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算按钮区域宽度
|
||||||
|
int buttonAreaWidth = buttonNum * functionButtonWidth +
|
||||||
|
(buttonNum > 0 ? (buttonNum +1) * buttonMargin : 0);
|
||||||
|
|
||||||
|
// 计算文本区域宽度(包括边距)
|
||||||
|
int textAreaWidth = textWidth + textToBorderMargin * 2 ;
|
||||||
|
|
||||||
|
// 对话框宽度取两者中的较大值,并确保最小宽度
|
||||||
|
this->width = buttonAreaWidth > textAreaWidth ? buttonAreaWidth : textAreaWidth;
|
||||||
|
this->width = this->width > 200 ? this->width : 200;
|
||||||
|
|
||||||
|
// 计算对话框高度
|
||||||
|
// 高度 = 标题栏高度 + 文本区域高度 + 按钮区域高度 + 间距
|
||||||
|
int textAreaHeight = textHeight * (int)lines.size() + 5*((int)lines.size()-1); // 文本行高+行间距
|
||||||
|
this->height = closeButtonHeight + // 标题栏高度
|
||||||
|
titleToTextMargin + // 标题到文本的间距
|
||||||
|
textAreaHeight + // 文本区域高度
|
||||||
|
buttonAreaHeight; // 按钮区域高度
|
||||||
|
|
||||||
|
// 居中定位对话框
|
||||||
|
this->x = (hWnd.getWidth() - this->width) / 2;
|
||||||
|
this->y = (hWnd.getHeight() - this->height) / 2;
|
||||||
|
|
||||||
|
//this->textStyle.nWidth = 10;
|
||||||
|
this->textStyle.nHeight = 20;
|
||||||
|
|
||||||
|
initButtons(); // 初始化按钮
|
||||||
|
initTitle(); // 初始化标题标签
|
||||||
|
initCloseButton(); // 初始化关闭按钮
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dialog::saveBackground(int x, int y, int w, int h)
|
||||||
|
{
|
||||||
|
if (w <= 0 || h <= 0) return;
|
||||||
|
saveBkX = x; saveBkY = y; saveWidth = w; saveHeight = h;
|
||||||
|
if (saveBkImage)
|
||||||
|
{
|
||||||
|
//尺寸变了才重建,避免反复 new/delete
|
||||||
|
if (saveBkImage->getwidth() != w || saveBkImage->getheight() != h)
|
||||||
|
{
|
||||||
|
delete saveBkImage; saveBkImage = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!saveBkImage) saveBkImage = new IMAGE(w + BorderWidth*2, h + BorderWidth*2);
|
||||||
|
|
||||||
|
SetWorkingImage(nullptr); // ★抓屏幕
|
||||||
|
getimage(saveBkImage, x - BorderWidth, y - BorderWidth, w + BorderWidth*2, h+ BorderWidth*2);
|
||||||
|
hasSnap = true;
|
||||||
|
}
|
||||||
|
void Dialog::restBackground()
|
||||||
|
{
|
||||||
|
if (!hasSnap || !saveBkImage) return;
|
||||||
|
// 直接回贴屏幕(与抓取一致)
|
||||||
|
SetWorkingImage(nullptr);
|
||||||
|
putimage(saveBkX - BorderWidth, saveBkY - BorderWidth,saveBkImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dialog::addControl(std::unique_ptr<Control> control)
|
||||||
|
{
|
||||||
|
control->setParent(this);
|
||||||
|
controls.push_back(std::move(control));
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 延迟清理策略:由于对话框绘制时保存了背景快照,必须在对话框隐藏后、
|
||||||
|
// 所有控件析构前恢复背景,否则会导致背景图像被错误覆盖。
|
||||||
|
// 此方法在对话框不可见且被标记为待清理时由 draw() 或 handleEvent() 调用。
|
||||||
|
void Dialog::performDelayedCleanup()
|
||||||
|
{
|
||||||
|
if (isCleaning) return;
|
||||||
|
|
||||||
|
isCleaning = true;
|
||||||
|
|
||||||
|
auto& c = hWnd.getControls();
|
||||||
|
for (auto& control : c)
|
||||||
|
control->setDirty(true);
|
||||||
|
|
||||||
|
controls.clear();
|
||||||
|
|
||||||
|
// 重置指针
|
||||||
|
closeButton = nullptr;
|
||||||
|
title.reset();
|
||||||
|
// 释放背景图像资源
|
||||||
|
if (saveBkImage && hasSnap)
|
||||||
|
{
|
||||||
|
restBackground();
|
||||||
|
FlushBatchDraw();
|
||||||
|
discardBackground();
|
||||||
|
}
|
||||||
|
if (!(saveBkImage && hasSnap))
|
||||||
|
{
|
||||||
|
// 没有背景快照:强制一次完整重绘,立即擦掉残影
|
||||||
|
hWnd.pumpResizeIfNeeded(); // 如果正好有尺寸标志,顺便统一收口
|
||||||
|
// 即使没有尺寸变化,也重绘一帧
|
||||||
|
BeginBatchDraw();
|
||||||
|
// 背景
|
||||||
|
if (hWnd.getBkImage() && !hWnd.getBkImageFile().empty())
|
||||||
|
putimage(0, 0, hWnd.getBkImage());
|
||||||
|
else { setbkcolor(hWnd.getBkcolor()); cleardevice(); }
|
||||||
|
// 所有普通控件
|
||||||
|
for (auto& c : hWnd.getControls()) c->draw();
|
||||||
|
// 其他对话框(this 已经 show=false,会早退不绘)
|
||||||
|
// 注意:此处若有容器管理,需要按你的现状遍历 dialogs 再 draw
|
||||||
|
EndBatchDraw();
|
||||||
|
FlushBatchDraw();
|
||||||
|
}
|
||||||
|
// 重置状态
|
||||||
|
needsInitialization = true;
|
||||||
|
pendingCleanup = false;
|
||||||
|
isCleaning = false;
|
||||||
|
shouldClose = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dialog::SetResultCallback(std::function<void(StellarX::MessageBoxResult)> cb)
|
||||||
|
{
|
||||||
|
resultCallback = std::move(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Dialog::GetCaption() const
|
||||||
|
{
|
||||||
|
return titleText;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Dialog::GetText() const
|
||||||
|
{
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dialog::clearControls()
|
||||||
|
{
|
||||||
|
controls.clear();
|
||||||
|
// 重置按钮指针
|
||||||
|
closeButton = nullptr;
|
||||||
|
title.reset(); // 释放标题资源
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Button> Dialog::createDialogButton(int x, int y, const std::string& text)
|
||||||
|
{
|
||||||
|
auto btn = std::make_unique<Button>(
|
||||||
|
x, y, functionButtonWidth, functionButtonHeight,
|
||||||
|
text,
|
||||||
|
buttonTrueColor, // 点击色
|
||||||
|
buttonFalseColor, // 背景色
|
||||||
|
buttonHoverColor, // 悬停色
|
||||||
|
StellarX::ButtonMode::NORMAL,
|
||||||
|
StellarX::ControlShape::RECTANGLE
|
||||||
|
);
|
||||||
|
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dialog::requestRepaint(Control* parent)
|
||||||
|
{
|
||||||
|
if (this == parent)
|
||||||
|
{
|
||||||
|
for (auto& control : controls)
|
||||||
|
if (control->isDirty() && control->IsVisible())
|
||||||
|
control->draw();
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
onRequestRepaintAsRoot();
|
||||||
|
}
|
||||||
34
src/MessageBox.cpp
Normal file
34
src/MessageBox.cpp
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#include "MessageBox.h"
|
||||||
|
|
||||||
|
namespace StellarX
|
||||||
|
{
|
||||||
|
MessageBoxResult MessageBox::showModal(Window& wnd,const std::string& text,const std::string& caption,
|
||||||
|
MessageBoxType type)
|
||||||
|
{
|
||||||
|
Dialog dlg(wnd, caption, text, type, true); // 模态
|
||||||
|
dlg.setInitialization(true);
|
||||||
|
dlg.Show();
|
||||||
|
return dlg.GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageBox::showAsync(Window& wnd,const std::string& text,const std::string& caption,MessageBoxType type,
|
||||||
|
std::function<void(MessageBoxResult)> onResult)
|
||||||
|
{
|
||||||
|
//去重,如果窗口内已有相同的对话框被触发,则不再创建
|
||||||
|
if (wnd.hasNonModalDialogWithCaption(caption, text))
|
||||||
|
{
|
||||||
|
std::cout << "\a" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto dlg = std::make_unique<Dialog>(wnd, caption, text,
|
||||||
|
type, false); // 非模态
|
||||||
|
Dialog* dlgPtr = dlg.get();
|
||||||
|
dlgPtr->setInitialization(true);
|
||||||
|
// 设置回调
|
||||||
|
if (onResult)
|
||||||
|
dlgPtr->SetResultCallback(std::move(onResult));
|
||||||
|
// 交给 Window 管理生命周期
|
||||||
|
wnd.addDialog(std::move(dlg));
|
||||||
|
dlgPtr->Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
398
src/TabControl.cpp
Normal file
398
src/TabControl.cpp
Normal file
@@ -0,0 +1,398 @@
|
|||||||
|
#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;
|
||||||
|
// 在绘制 TabControl 之前,先恢复并更新背景快照:
|
||||||
|
if (hasSnap)
|
||||||
|
{
|
||||||
|
// 先回贴旧快照,清除之前的绘制
|
||||||
|
restBackground();
|
||||||
|
// 如位置或尺寸变化,丢弃旧快照并重新抓取
|
||||||
|
if (!saveBkImage || saveBkX != this->x || saveBkY != this->y || saveWidth != this->width || saveHeight != this->height)
|
||||||
|
{
|
||||||
|
discardBackground();
|
||||||
|
saveBackground(this->x, this->y, this->width, this->height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 首次绘制时抓取背景
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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(parent);
|
||||||
|
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();
|
||||||
|
// 根据当前 TabControl 的新尺寸重新计算页签栏和页面区域
|
||||||
|
initTabBar();
|
||||||
|
initTabPage();
|
||||||
|
// 转发窗口尺寸变化给所有页签按钮和页面
|
||||||
|
for (auto& c : controls)
|
||||||
|
{
|
||||||
|
c.first->onWindowResize();
|
||||||
|
c.second->onWindowResize();
|
||||||
|
}
|
||||||
|
// 尺寸变化后需要重绘自身
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabControl::setDirty(bool dirty)
|
||||||
|
{
|
||||||
|
this->dirty = dirty;
|
||||||
|
for (auto& c : controls)
|
||||||
|
{
|
||||||
|
c.first->setDirty(dirty);
|
||||||
|
c.second->setDirty(dirty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabControl::requestRepaint(Control* parent)
|
||||||
|
{
|
||||||
|
if (this == parent)
|
||||||
|
{
|
||||||
|
for (auto& control : controls)
|
||||||
|
{
|
||||||
|
if (control.first->isDirty() && control.first->IsVisible())
|
||||||
|
control.first->draw();
|
||||||
|
else if (control.second->isDirty()&&control.second->IsVisible())
|
||||||
|
control.second->draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
onRequestRepaintAsRoot();
|
||||||
|
}
|
||||||
@@ -1,55 +1,75 @@
|
|||||||
#include "StellarX/Label.h"
|
#include "Label.h"
|
||||||
|
|
||||||
Label::Label()
|
Label::Label()
|
||||||
:Control(0, 0, 0, 0)
|
:Control(0, 0, 0, 0)
|
||||||
{
|
{
|
||||||
|
this->id = "Label";
|
||||||
this->text = "默认标签";
|
this->text = "默认标签";
|
||||||
textColor = RGB(0,0,0);
|
textStyle.color = RGB(0,0,0);
|
||||||
textBkColor = RGB(255, 255, 255);; //默认白色背景
|
textBkColor = RGB(255, 255, 255);; //默认白色背景
|
||||||
}
|
}
|
||||||
|
|
||||||
Label::Label(int x, int y, std::string text, COLORREF textcolor, COLORREF bkColor)
|
Label::Label(int x, int y, std::string text, COLORREF textcolor, COLORREF bkColor)
|
||||||
:Control(x, y, 0, 0)
|
:Control(x, y, 0, 0)
|
||||||
{
|
{
|
||||||
|
this->id = "Label";
|
||||||
this->text = text;
|
this->text = text;
|
||||||
textColor = textcolor;
|
textStyle.color = textcolor;
|
||||||
textBkColor = bkColor; //默认白色背景
|
textBkColor = bkColor; //默认白色背景
|
||||||
}
|
}
|
||||||
|
|
||||||
void Label::draw()
|
void Label::draw()
|
||||||
{
|
{
|
||||||
saveStyle();
|
if (dirty && show)
|
||||||
if (textBkDisap)
|
|
||||||
setbkmode(TRANSPARENT); //设置背景透明
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
setbkmode(OPAQUE); //设置背景不透明
|
saveStyle();
|
||||||
setbkcolor(textBkColor); //设置背景颜色
|
if (textBkDisap)
|
||||||
|
setbkmode(TRANSPARENT); //设置背景透明
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setbkmode(OPAQUE); //设置背景不透明
|
||||||
|
setbkcolor(textBkColor); //设置背景颜色
|
||||||
|
}
|
||||||
|
settextcolor(textStyle.color);
|
||||||
|
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
||||||
|
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
||||||
|
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); //设置字体样式
|
||||||
|
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()));
|
||||||
|
restoreStyle();
|
||||||
|
dirty = false;
|
||||||
}
|
}
|
||||||
settextcolor(textColor);
|
|
||||||
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
|
||||||
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
|
||||||
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); //设置字体样式
|
|
||||||
outtextxy(x,y, LPCTSTR(text.c_str()));
|
|
||||||
restoreStyle();
|
|
||||||
}
|
}
|
||||||
|
//用于“隐藏提示框”时调用(还原并释放快照)
|
||||||
|
void Label::hide()
|
||||||
|
{
|
||||||
|
restBackground(); // 还原屏幕像素
|
||||||
|
discardBackground(); // 作废快照,防止错贴旧图
|
||||||
|
dirty = false;
|
||||||
|
}
|
||||||
void Label::setTextdisap(bool key)
|
void Label::setTextdisap(bool key)
|
||||||
{
|
{
|
||||||
textBkDisap = key;
|
textBkDisap = key;
|
||||||
}
|
this->dirty = true;
|
||||||
|
|
||||||
void Label::setTextColor(COLORREF color)
|
|
||||||
{
|
|
||||||
textColor = color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Label::setTextBkColor(COLORREF color)
|
void Label::setTextBkColor(COLORREF color)
|
||||||
{
|
{
|
||||||
textBkColor = color;
|
textBkColor = color;
|
||||||
|
this->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Label::setText(std::string text)
|
void Label::setText(std::string text)
|
||||||
{
|
{
|
||||||
this->text = text;
|
this->text = text;
|
||||||
|
this->dirty = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
532
src/table.cpp
532
src/table.cpp
@@ -1,162 +1,296 @@
|
|||||||
#include "StellarX/Table.h"
|
#include "Table.h"
|
||||||
// 绘制表格的当前页
|
// 绘制表格的当前页
|
||||||
// 使用双循环绘制行和列,考虑分页偏移
|
// 使用双循环绘制行和列,考虑分页偏移
|
||||||
void Table::drawTable()
|
void Table::drawTable()
|
||||||
{
|
{
|
||||||
dX = x;
|
const int border = tableBorderWidth > 0 ? tableBorderWidth : 0;
|
||||||
dY = uY;
|
|
||||||
uY = dY + lineHeights.at(0) + 10;
|
|
||||||
|
|
||||||
for (int i = (currentPage * rowsPerPage - rowsPerPage); i < (currentPage*rowsPerPage) && i < data.size(); i++)
|
|
||||||
{
|
|
||||||
for (int j = 0; j < data[i].size(); j++)
|
|
||||||
{
|
|
||||||
uX = dX + colWidths.at(j) + 20;
|
|
||||||
fillrectangle(dX, dY, uX, uY);
|
|
||||||
outtextxy(dX + 10, dY + 5, LPCTSTR(data[i][j].c_str()));
|
|
||||||
dX += this->colWidths.at(j) + 20;
|
|
||||||
}
|
|
||||||
dX = x;
|
|
||||||
dY = uY;
|
|
||||||
uY = dY + lineHeights.at(0) + 10;
|
|
||||||
|
|
||||||
|
// 表体从“表头之下”开始
|
||||||
|
dX = x + border;
|
||||||
|
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();
|
||||||
|
|
||||||
|
for (size_t i = startRow; i < endRow; ++i)
|
||||||
|
{
|
||||||
|
for (size_t j = 0; j < data[i].size(); ++j)
|
||||||
|
{
|
||||||
|
uX = dX + colWidths.at(j) + TABLE_COL_GAP;
|
||||||
|
fillrectangle(dX, dY, uX, uY);
|
||||||
|
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) + TABLE_ROW_EXTRA;
|
||||||
}
|
}
|
||||||
uY = y + lineHeights.at(0) + 10;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::drawHeader()
|
void Table::drawHeader()
|
||||||
{
|
{
|
||||||
|
|
||||||
uY = dY + lineHeights.at(0) + 10;
|
const int border = tableBorderWidth > 0 ? tableBorderWidth : 0;
|
||||||
for(int i = 0; i < headers.size(); i++)
|
// 内容区原点 = x+border, y+border
|
||||||
{
|
dX = x + border;
|
||||||
uX = dX + colWidths.at(i) + 20;
|
dY = y + border;
|
||||||
fillrectangle(dX, dY, uX, uY);
|
uY = dY + lineHeights.at(0) + TABLE_HEADER_EXTRA;
|
||||||
outtextxy(dX + 10, dY + 5, LPCTSTR(headers[i].c_str()));
|
|
||||||
dX += this->colWidths.at(i) + 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for (size_t i = 0; i < headers.size(); i++)
|
||||||
|
{
|
||||||
|
uX = dX + colWidths.at(i) + TABLE_COL_GAP; // 注意这里是 +20,和表体一致
|
||||||
|
fillrectangle(dX, dY, uX, uY);
|
||||||
|
outtextxy(dX + TABLE_PAD_X, dY + TABLE_PAD_Y, LPCTSTR(headers[i].c_str()));
|
||||||
|
dX += colWidths.at(i) + TABLE_COL_GAP; // 列间距 20
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 初始化文本宽度和高度计算
|
// 遍历所有数据单元和表头,计算每列的最大宽度和每行的最大高度,
|
||||||
// 遍历所有数据和表头,计算每列的最大宽度和行高
|
// 为后续绘制表格单元格提供尺寸依据。此计算在数据变更时自动触发。
|
||||||
// 此方法在数据变更时自动调用
|
|
||||||
void Table::initTextWaH()
|
void Table::initTextWaH()
|
||||||
{
|
{
|
||||||
this->colWidths.resize(this->headers.size());
|
// 和绘制一致的单元内边距
|
||||||
this->lineHeights.resize(this->headers.size());
|
const int padX = TABLE_PAD_X; // 左右 padding
|
||||||
int width = 0;
|
const int padY = TABLE_PAD_Y; // 上下 padding
|
||||||
int height = 0;
|
const int colGap = TABLE_COL_GAP; // 列间距
|
||||||
//计算数据尺寸
|
const int border = tableBorderWidth > 0 ? tableBorderWidth : 0;
|
||||||
for (int i = 0; i < data.size(); i++)
|
|
||||||
|
// 统计每列最大文本宽 & 每列最大行高(包含数据 + 表头)
|
||||||
|
colWidths.assign(headers.size(), 0);
|
||||||
|
lineHeights.assign(headers.size(), 0);
|
||||||
|
|
||||||
|
// 先看数据
|
||||||
|
for (size_t i = 0; i < data.size(); ++i)
|
||||||
{
|
{
|
||||||
for (int j = 0; j < data[i].size(); j++)
|
for (size_t j = 0; j < data[i].size(); ++j)
|
||||||
{
|
{
|
||||||
width = textwidth(LPCTSTR(data[i][j].c_str()));
|
const int w = textwidth(LPCTSTR(data[i][j].c_str()));
|
||||||
height = textheight(LPCTSTR(data[i][j].c_str()));
|
const int h = textheight(LPCTSTR(data[i][j].c_str()));
|
||||||
if (width > this->colWidths.at(j))
|
if (w > colWidths[j]) colWidths[j] = w;
|
||||||
this->colWidths.at(j) = width;
|
if (h > lineHeights[j]) lineHeights[j] = h;
|
||||||
if (height > this->lineHeights[j])
|
|
||||||
this->lineHeights.at(j) = height;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 再用表头更新(谁大取谁)
|
||||||
for (int i = 0; i < this->headers.size(); i++)
|
for (size_t j = 0; j < headers.size(); ++j)
|
||||||
{
|
{
|
||||||
width = textwidth(LPCTSTR(headers[i].c_str()));
|
const int w = textwidth(LPCTSTR(headers[j].c_str()));
|
||||||
height = textheight(LPCTSTR(headers[i].c_str()));
|
const int h = textheight(LPCTSTR(headers[j].c_str()));
|
||||||
if (width > this->colWidths.at(i))
|
if (w > colWidths[j]) colWidths[j] = w;
|
||||||
this->colWidths.at(i) = width;
|
if (h > lineHeights[j]) lineHeights[j] = h;
|
||||||
if (height > this->lineHeights[i])
|
|
||||||
this->lineHeights.at(i) = height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算表格总宽度和高度
|
// 用“所有列的最大行高”作为一行的基准高度
|
||||||
this->width = 0;
|
int maxLineH = 0;
|
||||||
for (int i = 0; i < colWidths.size(); i++)
|
|
||||||
this->width += colWidths.at(i) + 20;
|
|
||||||
LINESTYLE currentStyle;
|
|
||||||
|
|
||||||
this->width += tableBorderWidth;
|
for (int h : lineHeights)
|
||||||
|
if (h > maxLineH)
|
||||||
|
maxLineH = h;
|
||||||
|
|
||||||
this->height = lineHeights.at(0) * (rowsPerPage + 1) + rowsPerPage * 10+20 ; // 表头+数据行+页码区域
|
// 列宽包含左右 padding:在计算完最大文本宽度后,加上 2*padX 作为单元格内边距
|
||||||
|
for (size_t j = 0; j < colWidths.size(); ++j) {
|
||||||
|
colWidths[j] += 2 * padX;
|
||||||
|
}
|
||||||
|
|
||||||
// 如果背景图像不存在或尺寸不匹配,创建或重新创建
|
// 表内容总宽 = Σ(列宽 + 列间距)
|
||||||
if (saveBkImage == nullptr) {
|
int contentW = 0;
|
||||||
saveBkImage = new IMAGE(width, height);
|
for (size_t j = 0; j < colWidths.size(); ++j)
|
||||||
}
|
contentW += colWidths[j] + colGap;
|
||||||
else if (saveBkImage->getwidth() != width || saveBkImage->getheight() != height) {
|
|
||||||
delete saveBkImage;
|
// 表头高 & 行高(与 drawHeader/drawTable 内部一致:+上下 padding)
|
||||||
saveBkImage = new IMAGE(width, height);
|
const int headerH = maxLineH + 2 * padY;
|
||||||
}
|
const int rowH = maxLineH + 2 * padY;
|
||||||
|
const int rowsH = rowH * rowsPerPage;
|
||||||
|
|
||||||
|
// 页脚:
|
||||||
|
const int pageTextH = textheight(LPCTSTR(pageNumtext.c_str()));
|
||||||
|
const int btnTextH = textheight(LPCTSTR("上一页"));
|
||||||
|
const int btnPadV = TABLE_BTN_TEXT_PAD_V;
|
||||||
|
const int btnH = btnTextH + 2 * btnPadV;
|
||||||
|
const int footerPad = TABLE_FOOTER_PAD;
|
||||||
|
const int footerH = (pageTextH > btnH ? pageTextH : btnH) + footerPad;
|
||||||
|
|
||||||
|
// 最终表宽/高:内容 + 对称边框
|
||||||
|
this->width = contentW + (border << 1);
|
||||||
|
this->height = headerH + rowsH + footerH + (border << 1);
|
||||||
|
// 记录原始宽高用于锚点布局的参考;此处仅在初始化单元尺寸时重置
|
||||||
|
this->localWidth = this->width;
|
||||||
|
this->localHeight = this->height;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::initButton()
|
void Table::initButton()
|
||||||
{
|
{
|
||||||
int x1, x2;
|
const int gap = TABLE_BTN_GAP;
|
||||||
int y1, y2;
|
const int padH = TABLE_BTN_PAD_H;
|
||||||
x1 = pX - 70;
|
const int padV = TABLE_BTN_PAD_V; // 按钮垂直内边距
|
||||||
x2 = pX + textwidth(LPCTSTR(pageNumtext.c_str())) + 10;
|
|
||||||
y1 = y2 = pY;
|
int pageW = textwidth(LPCTSTR(pageNumtext.c_str()));
|
||||||
this->prevButton = new Button(x1, y1, 60, textheight(LPCTSTR(pageNumtext.c_str())), "上一页", RGB(0, 0, 0), RGB(255, 255, 255));
|
int lblH = textheight(LPCTSTR(pageNumtext.c_str()));
|
||||||
this->nextButton = new Button(x2, y2, 60, textheight(LPCTSTR(pageNumtext.c_str())), "下一页", RGB(0, 0, 0), RGB(255, 255, 255));
|
|
||||||
prevButton->setOnClickListener([this]()
|
// 统一按钮尺寸(用按钮文字自身宽高 + padding)
|
||||||
{if (this->currentPage > 1)
|
int prevW = textwidth(LPCTSTR(TABLE_STR_PREV)) + padH * 2;
|
||||||
{
|
int nextW = textwidth(LPCTSTR(TABLE_STR_NEXT)) + padH * 2;
|
||||||
this->currentPage--;
|
int btnH = lblH + padV * 2;
|
||||||
this->dirty = true;
|
|
||||||
this->draw();
|
|
||||||
} });
|
|
||||||
|
|
||||||
nextButton->setOnClickListener([this]()
|
|
||||||
{if (this->currentPage < (this->totalPages))
|
// 基于“页码标签”的矩形来摆放:
|
||||||
|
// prev 在页码左侧 gap 处;next 在右侧 gap 处;Y 对齐 pY
|
||||||
|
int prevX = pX - gap - prevW;
|
||||||
|
int nextX = pX + pageW + gap;
|
||||||
|
int btnY = pY; // 和页码同一基线
|
||||||
|
|
||||||
|
if (!prevButton)
|
||||||
|
prevButton = new Button(prevX, btnY, prevW, btnH, TABLE_STR_PREV, RGB(0, 0, 0), RGB(255, 255, 255));
|
||||||
|
else
|
||||||
{
|
{
|
||||||
this->currentPage++;
|
prevButton->setX(prevX);
|
||||||
this->dirty = true;
|
prevButton->setY(btnY);
|
||||||
this->draw();
|
}
|
||||||
}});
|
|
||||||
|
if (!nextButton)
|
||||||
|
nextButton = new Button(nextX, btnY, nextW, btnH, TABLE_STR_NEXT, RGB(0, 0, 0), RGB(255, 255, 255));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nextButton->setX(nextX);
|
||||||
|
nextButton->setY(btnY);
|
||||||
|
}
|
||||||
|
|
||||||
|
prevButton->textStyle = this->textStyle;
|
||||||
|
nextButton->textStyle = this->textStyle;
|
||||||
|
prevButton->setFillMode(tableFillMode);
|
||||||
|
nextButton->setFillMode(tableFillMode);
|
||||||
|
|
||||||
|
prevButton->setOnClickListener([this]()
|
||||||
|
{
|
||||||
|
if (currentPage > 1)
|
||||||
|
{
|
||||||
|
--currentPage;
|
||||||
|
dirty = true;
|
||||||
|
if (pageNum) pageNum->setDirty(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
nextButton->setOnClickListener([this]()
|
||||||
|
{
|
||||||
|
if (currentPage < totalPages)
|
||||||
|
{
|
||||||
|
++currentPage;
|
||||||
|
dirty = true;
|
||||||
|
if (pageNum) pageNum->setDirty(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
isNeedButtonAndPageNum = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::initPageNum()
|
void Table::initPageNum()
|
||||||
{
|
{
|
||||||
if (0 == pY)
|
// 统一坐标系
|
||||||
pY = uY + lineHeights.at(0) * rowsPerPage + rowsPerPage * 10+10;
|
const int border = tableBorderWidth > 0 ? tableBorderWidth : 0;
|
||||||
for (int i = 0; i < colWidths.size(); i++)
|
const int baseH = lineHeights.empty() ? 0 : lineHeights.at(0);
|
||||||
this->pX += colWidths.at(i) + 20;
|
const int headerH = baseH + TABLE_HEADER_EXTRA;
|
||||||
this->pX -= textwidth(LPCTSTR(pageNumtext.c_str()));
|
const int rowsH = baseH * rowsPerPage + rowsPerPage * TABLE_ROW_EXTRA;
|
||||||
this->pX /= 2;
|
|
||||||
this->pX += x;
|
// 内容宽度 = sum(colWidths + 20);initTextWaH() 已把 this->width += 2*border
|
||||||
this->pageNum = new Label(this->pX, pY, pageNumtext);
|
// 因此 contentW = this->width - 2*border 更稳妥
|
||||||
//pageNum->setTextdisap(true);
|
const int contentW = this->width - (border << 1);
|
||||||
|
|
||||||
|
// 页脚顶部位置(表头 + 可视数据区 之后)
|
||||||
|
pY = y + border + headerH + rowsH + TABLE_FOOTER_BLANK; // +8 顶部留白
|
||||||
|
|
||||||
|
// 按理来说 x + (this->width - textW) / 2;就可以
|
||||||
|
// 但是在绘制时,发现控件偏右,因此减去40
|
||||||
|
int textW = textwidth(LPCTSTR(pageNumtext.c_str()));
|
||||||
|
pX = x + TABLE_PAGE_TEXT_OFFSET_X +(this->width - textW) / 2;
|
||||||
|
|
||||||
|
if (!pageNum)
|
||||||
|
pageNum = new Label(pX, pY, pageNumtext);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pageNum->setX(pX);
|
||||||
|
pageNum->setY(pY);
|
||||||
|
}
|
||||||
|
|
||||||
pageNum->textStyle = this->textStyle;
|
pageNum->textStyle = this->textStyle;
|
||||||
|
if (StellarX::FillMode::Null == tableFillMode)
|
||||||
|
pageNum->setTextdisap(true); // 透明文本
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::drawPageNum()
|
void Table::drawPageNum()
|
||||||
{
|
{
|
||||||
if (nullptr == pageNum)
|
|
||||||
initPageNum();
|
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 += "页";
|
||||||
|
if (nullptr == pageNum || isNeedButtonAndPageNum)
|
||||||
|
initPageNum();
|
||||||
pageNum->setText(pageNumtext);
|
pageNum->setText(pageNumtext);
|
||||||
|
pageNum->textStyle = this->textStyle;
|
||||||
|
if (StellarX::FillMode::Null == tableFillMode)
|
||||||
|
pageNum->setTextdisap(true);
|
||||||
pageNum->draw();
|
pageNum->draw();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::drawButton()
|
void Table::drawButton()
|
||||||
{
|
{
|
||||||
if (nullptr == prevButton || nullptr == nextButton)
|
if ((nullptr == prevButton || nullptr == nextButton)|| isNeedButtonAndPageNum)
|
||||||
initButton();
|
initButton();
|
||||||
|
|
||||||
|
this->prevButton->textStyle = this->textStyle;
|
||||||
|
this->nextButton->textStyle = this->textStyle;
|
||||||
|
this->prevButton->setFillMode(tableFillMode);
|
||||||
|
this->nextButton->setFillMode(tableFillMode);
|
||||||
|
this->prevButton->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
|
||||||
|
this->nextButton->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
|
||||||
|
this->prevButton->setDirty(true);
|
||||||
|
this->nextButton->setDirty(true);
|
||||||
prevButton->draw();
|
prevButton->draw();
|
||||||
nextButton->draw();
|
nextButton->draw();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Table::setWidth(int width)
|
||||||
|
{
|
||||||
|
// 调整列宽以匹配新的表格总宽度。不修改 localWidth,避免累计误差。
|
||||||
|
// 当 width 与当前 width 不同时,根据差值平均分配到各列,余数依次累加/扣减。
|
||||||
|
const int ncols = static_cast<int>(colWidths.size());
|
||||||
|
if (ncols <= 0) {
|
||||||
|
this->width = width;
|
||||||
|
isNeedButtonAndPageNum = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int diff = width - this->width;
|
||||||
|
// 基础增量:整除部分
|
||||||
|
int baseChange = diff / ncols;
|
||||||
|
int remainder = diff % ncols;
|
||||||
|
for (int i = 0; i < ncols; ++i) {
|
||||||
|
int change = baseChange;
|
||||||
|
if (remainder > 0) {
|
||||||
|
change += 1;
|
||||||
|
remainder -= 1;
|
||||||
|
} else if (remainder < 0) {
|
||||||
|
change -= 1;
|
||||||
|
remainder += 1;
|
||||||
|
}
|
||||||
|
int newWidth = colWidths[i] + change;
|
||||||
|
// 限制最小宽度为 1,防止出现负值
|
||||||
|
if (newWidth < 1) newWidth = 1;
|
||||||
|
colWidths[i] = newWidth;
|
||||||
|
}
|
||||||
|
this->width = width;
|
||||||
|
// 需要重新布局页脚元素
|
||||||
|
isNeedButtonAndPageNum = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Table::setHeight(int height)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
Table::Table(int x, int y)
|
Table::Table(int x, int y)
|
||||||
:Control(x, y, 0,0)
|
:Control(x, y, 0, 0)
|
||||||
{
|
{
|
||||||
//this->saveBkImage = new IMAGE(this->width,this->height);
|
this->id = "Table";
|
||||||
}
|
}
|
||||||
|
|
||||||
Table::~Table()
|
Table::~Table()
|
||||||
@@ -177,7 +311,29 @@ Table::~Table()
|
|||||||
|
|
||||||
void Table::draw()
|
void Table::draw()
|
||||||
{
|
{
|
||||||
if (this->dirty)
|
//在这里先初始化保证翻页按钮不为空
|
||||||
|
// 在一些容器中,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)
|
||||||
{
|
{
|
||||||
// 先保存当前绘图状态
|
// 先保存当前绘图状态
|
||||||
saveStyle();
|
saveStyle();
|
||||||
@@ -193,66 +349,54 @@ void Table::draw()
|
|||||||
setfillstyle((int)tableFillMode);
|
setfillstyle((int)tableFillMode);
|
||||||
setbkmode(TRANSPARENT);
|
setbkmode(TRANSPARENT);
|
||||||
|
|
||||||
// 是否需要计算单元格尺寸
|
if (isNeedDrawHeaders)
|
||||||
if (isNeedCellSize)
|
|
||||||
{
|
{
|
||||||
initTextWaH();
|
// 重新设置表格样式
|
||||||
isNeedCellSize = false;
|
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);
|
||||||
|
setbkmode(TRANSPARENT);
|
||||||
}
|
}
|
||||||
|
// 在绘制前先恢复并更新背景快照:
|
||||||
|
// 如果已有快照且尺寸发生变化,先恢复旧快照以清除上一次绘制,然后丢弃旧快照再重新抓取新的区域。
|
||||||
|
if (hasSnap)
|
||||||
|
{
|
||||||
|
// 始终先恢复旧背景,清除上一帧内容
|
||||||
|
restBackground();
|
||||||
|
// 当尺寸变化或缓存图像无效时,需要重新截图
|
||||||
|
if (!saveBkImage || saveWidth != this->width || saveHeight != this->height)
|
||||||
|
{
|
||||||
|
discardBackground();
|
||||||
|
saveBackground(this->x, this->y, this->width, this->height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 首次绘制时无背景缓存,直接抓取
|
||||||
|
saveBackground(this->x, this->y, this->width, this->height);
|
||||||
|
}
|
||||||
|
// 恢复最新的背景,保证绘制区域干净
|
||||||
|
restBackground();
|
||||||
|
// 绘制表头
|
||||||
|
|
||||||
// 在绘制表格之前捕获背景
|
dX = x;
|
||||||
// 只有在第一次绘制或者尺寸变化时才需要重新捕获背景
|
dY = y;
|
||||||
static bool firstDraw = true;
|
drawHeader();
|
||||||
if (firstDraw || isNeedDrawHeaders) {
|
this->isNeedDrawHeaders = false;
|
||||||
// 确保在绘制任何表格内容之前捕获背景
|
|
||||||
if (saveBkImage) {
|
|
||||||
// 临时恢复样式,确保捕获正确的背景
|
|
||||||
restoreStyle();
|
|
||||||
if(tableBorderWidth>1)
|
|
||||||
getimage(saveBkImage, this->x- tableBorderWidth, this->y- tableBorderWidth, this->width+ tableBorderWidth, this->height+ tableBorderWidth);
|
|
||||||
else
|
|
||||||
getimage(saveBkImage, this->x, this->y, this->width, this->height);
|
|
||||||
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);
|
|
||||||
setbkmode(TRANSPARENT);
|
|
||||||
}
|
|
||||||
firstDraw = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 恢复背景(清除旧内容)
|
|
||||||
if (saveBkImage) {
|
|
||||||
if (tableBorderWidth > 1)
|
|
||||||
putimage(this->x - tableBorderWidth, this->y - tableBorderWidth, saveBkImage);
|
|
||||||
else
|
|
||||||
putimage(this->x,this->y,this->saveBkImage);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绘制表头
|
|
||||||
|
|
||||||
dX = x;
|
|
||||||
dY = y;
|
|
||||||
drawHeader();
|
|
||||||
this->isNeedDrawHeaders = false;
|
|
||||||
|
|
||||||
|
|
||||||
// 绘制当前页
|
// 绘制当前页
|
||||||
drawTable();
|
drawTable();
|
||||||
|
|
||||||
// 绘制页码标签
|
// 绘制页码标签
|
||||||
drawPageNum();
|
drawPageNum();
|
||||||
|
|
||||||
// 绘制翻页按钮
|
// 绘制翻页按钮
|
||||||
if (this->isShowPageButton)
|
if (this->isShowPageButton)
|
||||||
drawButton();
|
drawButton();
|
||||||
|
|
||||||
|
|
||||||
// 恢复绘图状态
|
// 恢复绘图状态
|
||||||
restoreStyle();
|
restoreStyle();
|
||||||
@@ -260,42 +404,59 @@ void Table::draw()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::handleEvent(const ExMessage& msg)
|
bool Table::handleEvent(const ExMessage& msg)
|
||||||
{
|
{
|
||||||
|
if(!show)return false;
|
||||||
|
bool consume = false;
|
||||||
if(!this->isShowPageButton)
|
if(!this->isShowPageButton)
|
||||||
return;
|
return consume;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
prevButton->handleEvent(msg);
|
if(prevButton)consume = prevButton->handleEvent(msg);
|
||||||
nextButton->handleEvent(msg);
|
if (nextButton&&!consume)
|
||||||
|
consume = nextButton->handleEvent(msg);
|
||||||
}
|
}
|
||||||
|
if (dirty)
|
||||||
|
requestRepaint(parent);
|
||||||
|
return consume;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::setHeaders(std::initializer_list<std::string> headers)
|
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);
|
||||||
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
||||||
isNeedDrawHeaders = true; // 标记需要重新绘制表头
|
isNeedDrawHeaders = true; // 标记需要重新绘制表头
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::setData(const std::vector<std::string>& data)
|
void Table::setData( std::vector<std::string> data)
|
||||||
{
|
{
|
||||||
|
if (data.size() < headers.size())
|
||||||
|
for (int i = 0; data.size() <= headers.size(); i++)
|
||||||
|
data.push_back("");
|
||||||
this->data.push_back(data);
|
this->data.push_back(data);
|
||||||
totalPages = (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;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::setData(const 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)
|
||||||
this->data.push_back(lis);
|
if (lis.size() < headers.size())
|
||||||
totalPages = (this->data.size() + rowsPerPage - 1) / rowsPerPage;
|
{
|
||||||
|
for (size_t i = lis.size(); i< headers.size(); i++)
|
||||||
|
lis.push_back("");
|
||||||
|
this->data.push_back(lis);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this->data.push_back(lis);
|
||||||
|
|
||||||
|
totalPages = ((int)this->data.size() + rowsPerPage - 1) / rowsPerPage;
|
||||||
if (totalPages < 1)
|
if (totalPages < 1)
|
||||||
totalPages = 1;
|
totalPages = 1;
|
||||||
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
||||||
@@ -305,7 +466,7 @@ void Table::setData(const std::initializer_list<std::vector<std::string>>& data)
|
|||||||
void Table::setRowsPerPage(int rows)
|
void Table::setRowsPerPage(int rows)
|
||||||
{
|
{
|
||||||
this->rowsPerPage = rows;
|
this->rowsPerPage = rows;
|
||||||
totalPages = (data.size() + rowsPerPage - 1) / rowsPerPage;
|
totalPages = ((int)data.size() + rowsPerPage - 1) / rowsPerPage;
|
||||||
if (totalPages < 1)
|
if (totalPages < 1)
|
||||||
totalPages = 1;
|
totalPages = 1;
|
||||||
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
||||||
@@ -315,16 +476,19 @@ void Table::setRowsPerPage(int rows)
|
|||||||
void Table::showPageButton(bool isShow)
|
void Table::showPageButton(bool isShow)
|
||||||
{
|
{
|
||||||
this->isShowPageButton = isShow;
|
this->isShowPageButton = isShow;
|
||||||
|
this->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::setTableBorder(COLORREF color)
|
void Table::setTableBorder(COLORREF color)
|
||||||
{
|
{
|
||||||
this->tableBorderClor = color;
|
this->tableBorderClor = color;
|
||||||
|
this->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::setTableBk(COLORREF color)
|
void Table::setTableBk(COLORREF color)
|
||||||
{
|
{
|
||||||
this->tableBkClor = color;
|
this->tableBkClor = color;
|
||||||
|
this->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::setTableFillMode(StellarX::FillMode mode)
|
void Table::setTableFillMode(StellarX::FillMode mode)
|
||||||
@@ -333,16 +497,41 @@ void Table::setTableFillMode(StellarX::FillMode mode)
|
|||||||
this->tableFillMode = mode;
|
this->tableFillMode = mode;
|
||||||
else
|
else
|
||||||
this->tableFillMode = StellarX::FillMode::Solid;
|
this->tableFillMode = StellarX::FillMode::Solid;
|
||||||
|
if (this->prevButton && this->nextButton && this->pageNum)
|
||||||
|
{
|
||||||
|
this->prevButton->textStyle = this->textStyle;
|
||||||
|
this->nextButton->textStyle = this->textStyle;
|
||||||
|
this->prevButton->setFillMode(tableFillMode);
|
||||||
|
this->nextButton->setFillMode(tableFillMode);
|
||||||
|
if (StellarX::FillMode::Null == tableFillMode)
|
||||||
|
pageNum->setTextdisap(true);
|
||||||
|
this->prevButton->setDirty(true);
|
||||||
|
this->nextButton->setDirty(true);
|
||||||
|
}
|
||||||
|
this->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::setTableLineStyle(StellarX::LineStyle style)
|
void Table::setTableLineStyle(StellarX::LineStyle style)
|
||||||
{
|
{
|
||||||
this->tableLineStyle = style;
|
this->tableLineStyle = style;
|
||||||
|
this->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::setTableBorderWidth(int width)
|
void Table::setTableBorderWidth(int width)
|
||||||
{
|
{
|
||||||
this->tableBorderWidth = width;
|
this->tableBorderWidth = width;
|
||||||
|
this->dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Table::onWindowResize()
|
||||||
|
{
|
||||||
|
Control::onWindowResize(); // 先处理自己
|
||||||
|
if (this->prevButton && this->nextButton && this->pageNum)
|
||||||
|
{
|
||||||
|
prevButton->onWindowResize();
|
||||||
|
nextButton->onWindowResize();
|
||||||
|
pageNum->onWindowResize();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int Table::getCurrentPage() const
|
int Table::getCurrentPage() const
|
||||||
@@ -352,7 +541,7 @@ int Table::getCurrentPage() const
|
|||||||
|
|
||||||
int Table::getTotalPages() const
|
int Table::getTotalPages() const
|
||||||
{
|
{
|
||||||
return this->totalPages;;
|
return this->totalPages;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Table::getRowsPerPage() const
|
int Table::getRowsPerPage() const
|
||||||
@@ -400,4 +589,17 @@ int Table::getTableBorderWidth() const
|
|||||||
return this->tableBorderWidth;
|
return this->tableBorderWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Table::getTableWidth() const
|
||||||
|
{
|
||||||
|
int temp = 0;
|
||||||
|
for (auto& w : colWidths)
|
||||||
|
temp += w;
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Table::getTableHeight() const
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
// TextBox.cpp
|
// TextBox.cpp
|
||||||
#include "StellarX/TextBox.h"
|
#include "TextBox.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";
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBox::draw()
|
void TextBox::draw()
|
||||||
{
|
{
|
||||||
if(dirty)
|
if (dirty && show)
|
||||||
{
|
{
|
||||||
saveStyle();
|
saveStyle();
|
||||||
setfillcolor(textBoxBkClor);
|
setfillcolor(textBoxBkClor);
|
||||||
@@ -27,7 +28,10 @@ void TextBox::draw()
|
|||||||
int text_width = textwidth(LPCTSTR(text.c_str()));
|
int text_width = textwidth(LPCTSTR(text.c_str()));
|
||||||
int text_height = textheight(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)
|
switch (shape)
|
||||||
{
|
{
|
||||||
@@ -48,16 +52,17 @@ void TextBox::draw()
|
|||||||
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
restoreStyle();
|
||||||
|
dirty = false; //标记不需要重绘
|
||||||
}
|
}
|
||||||
restoreStyle();
|
|
||||||
dirty = false; //标记不需要重绘
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBox::handleEvent(const ExMessage& msg)
|
bool TextBox::handleEvent(const ExMessage& msg)
|
||||||
{
|
{
|
||||||
bool hover = false;
|
bool hover = false;
|
||||||
bool oldClick = click;
|
bool oldClick = click;
|
||||||
|
bool consume = false;
|
||||||
|
|
||||||
switch (shape)
|
switch (shape)
|
||||||
{
|
{
|
||||||
@@ -66,36 +71,44 @@ void TextBox::handleEvent(const ExMessage& msg)
|
|||||||
case StellarX::ControlShape::ROUND_RECTANGLE:
|
case StellarX::ControlShape::ROUND_RECTANGLE:
|
||||||
case StellarX::ControlShape::B_ROUND_RECTANGLE:
|
case StellarX::ControlShape::B_ROUND_RECTANGLE:
|
||||||
hover = (msg.x > x && msg.x < (x + width) && msg.y > y && msg.y < (y + height));//判断鼠标是否在矩形按钮内
|
hover = (msg.x > x && msg.x < (x + width) && msg.y > y && msg.y < (y + height));//判断鼠标是否在矩形按钮内
|
||||||
|
consume = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (hover && msg.message == WM_LBUTTONUP)
|
if (hover && msg.message == WM_LBUTTONUP)
|
||||||
{
|
{
|
||||||
click = true;
|
click = true;
|
||||||
if(StellarX::TextBoxmode::INPUT_MODE == mode)
|
if(StellarX::TextBoxmode::INPUT_MODE == mode)
|
||||||
dirty = InputBox(LPTSTR(text.c_str()), maxCharLen,"输入框",NULL,text.c_str(), NULL ,NULL,false);
|
{
|
||||||
|
dirty = InputBox(LPTSTR(text.c_str()), (int)maxCharLen, "输入框", NULL, text.c_str(), NULL, NULL, false);
|
||||||
|
consume = true;
|
||||||
|
}
|
||||||
else if (StellarX::TextBoxmode::READONLY_MODE == mode)
|
else if (StellarX::TextBoxmode::READONLY_MODE == mode)
|
||||||
{
|
{
|
||||||
dirty = false;
|
dirty = false;
|
||||||
InputBox(NULL, maxCharLen, "输出框(输入无效!)", NULL, text.c_str(), NULL, NULL, false);
|
InputBox(NULL, (int)maxCharLen, "输出框(输入无效!)", NULL, text.c_str(), NULL, NULL, false);
|
||||||
|
consume = true;
|
||||||
}
|
}
|
||||||
flushmessage(EX_MOUSE | EX_KEY);
|
flushmessage(EX_MOUSE | EX_KEY);
|
||||||
}
|
}
|
||||||
if (dirty)
|
if (dirty)
|
||||||
draw();
|
requestRepaint(parent);
|
||||||
|
|
||||||
if (click)
|
if (click)
|
||||||
click = false;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBox::setMaxCharLen(int len)
|
void TextBox::setMaxCharLen(size_t len)
|
||||||
{
|
{
|
||||||
if (len > 0)
|
if (len > 0)
|
||||||
maxCharLen = len;
|
maxCharLen = len;
|
||||||
|
this->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBox::setTextBoxshape(StellarX::ControlShape shape)
|
void TextBox::setTextBoxshape(StellarX::ControlShape shape)
|
||||||
@@ -107,12 +120,14 @@ void TextBox::setTextBoxshape(StellarX::ControlShape shape)
|
|||||||
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;
|
||||||
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;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,11 +135,22 @@ void TextBox::setTextBoxshape(StellarX::ControlShape shape)
|
|||||||
void TextBox::setTextBoxBorder(COLORREF color)
|
void TextBox::setTextBoxBorder(COLORREF color)
|
||||||
{
|
{
|
||||||
textBoxBorderClor = color;
|
textBoxBorderClor = color;
|
||||||
|
this->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBox::setTextBoxBk(COLORREF color)
|
void TextBox::setTextBoxBk(COLORREF color)
|
||||||
{
|
{
|
||||||
textBoxBkClor = color;
|
textBoxBkClor = color;
|
||||||
|
this->dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextBox::setText(std::string text)
|
||||||
|
{
|
||||||
|
if(text.size() > maxCharLen)
|
||||||
|
text = text.substr(0, maxCharLen);
|
||||||
|
this->text = text;
|
||||||
|
this->dirty = true;
|
||||||
|
draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string TextBox::getText() const
|
std::string TextBox::getText() const
|
||||||
|
|||||||
849
src/window.cpp
849
src/window.cpp
@@ -1,114 +1,819 @@
|
|||||||
#include "StellarX/Window.h"
|
#include "Window.h"
|
||||||
|
#include "Dialog.h"
|
||||||
|
|
||||||
Window::Window(int width, int height, int mode)
|
#include <easyx.h>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ApplyResizableStyle
|
||||||
|
* 作用:统一设置可拉伸/裁剪样式,并按开关使用 WS_EX_COMPOSITED(合成双缓冲)。
|
||||||
|
* 关键点:
|
||||||
|
* - WS_THICKFRAME:允许从四边/四角拖动改变尺寸。
|
||||||
|
* - WS_CLIPCHILDREN / WS_CLIPSIBLINGS:避免子控件互相覆盖时闪烁。
|
||||||
|
* - WS_EX_COMPOSITED:在一些环境更平滑,但个别显卡/驱动可能带来一帧延迟感。
|
||||||
|
* - SWP_FRAMECHANGED:通知窗口样式已变更,强制系统重算非客户区(标题栏/边框)。
|
||||||
|
*/
|
||||||
|
static void ApplyResizableStyle(HWND h, bool useComposited)
|
||||||
{
|
{
|
||||||
this->width = width;
|
LONG style = GetWindowLong(h, GWL_STYLE);
|
||||||
this->height = height;
|
style |= WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
|
||||||
this->windowMode = mode;
|
SetWindowLong(h, GWL_STYLE, style);
|
||||||
|
|
||||||
|
LONG ex = GetWindowLong(h, GWL_EXSTYLE);
|
||||||
|
if (useComposited)
|
||||||
|
{
|
||||||
|
ex |= WS_EX_COMPOSITED;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ex &= ~WS_EX_COMPOSITED;
|
||||||
|
}
|
||||||
|
SetWindowLong(h, GWL_EXSTYLE, ex);
|
||||||
|
|
||||||
|
SetWindowPos(h, NULL, 0, 0, 0, 0,
|
||||||
|
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
|
||||||
}
|
}
|
||||||
|
|
||||||
Window::Window(int width, int height, int mode, COLORREF bkcloc)
|
/**
|
||||||
|
* ApplyMinSizeOnSizing
|
||||||
|
* 作用:在 WM_SIZING 阶段执行“最小尺寸夹紧”。
|
||||||
|
* 规则:只回推“被拖动的那一侧”,另一侧当锚点(避免几何回弹/位置漂移)。
|
||||||
|
* 步骤:
|
||||||
|
* 1)将“最小客户区尺寸”通过 AdjustWindowRectEx 换算为“最小窗口矩形”(含非客户区)。
|
||||||
|
* 2)若当前矩形比最小还小,则根据 edge(哪条边/角在被拖)调整对应边,另一侧保持不动。
|
||||||
|
* 说明:仅保证不小于最小值;不做对齐/回滚等操作,把其余交给系统尺寸逻辑。
|
||||||
|
*/
|
||||||
|
static void ApplyMinSizeOnSizing(RECT* prc, WPARAM edge, HWND hWnd, int minClientW, int minClientH)
|
||||||
{
|
{
|
||||||
this->width = width;
|
RECT rcFrame{ 0, 0, minClientW, minClientH };
|
||||||
this->height = height;
|
DWORD style = GetWindowLong(hWnd, GWL_STYLE);
|
||||||
this->windowMode = mode;
|
DWORD ex = GetWindowLong(hWnd, GWL_EXSTYLE);
|
||||||
this->wBkcolor = bkcloc;
|
AdjustWindowRectEx(&rcFrame, style, FALSE, ex);
|
||||||
|
|
||||||
|
const int minW = rcFrame.right - rcFrame.left;
|
||||||
|
const int minH = rcFrame.bottom - rcFrame.top;
|
||||||
|
|
||||||
|
const int curW = prc->right - prc->left;
|
||||||
|
const int curH = prc->bottom - prc->top;
|
||||||
|
|
||||||
|
if (curW < minW)
|
||||||
|
{
|
||||||
|
switch (edge)
|
||||||
|
{
|
||||||
|
case WMSZ_LEFT:
|
||||||
|
case WMSZ_TOPLEFT:
|
||||||
|
case WMSZ_BOTTOMLEFT:
|
||||||
|
prc->left = prc->right - minW; // 锚定右侧,回推左侧(左边被拖)
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
prc->right = prc->left + minW; // 锚定左侧,回推右侧(右边被拖)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curH < minH)
|
||||||
|
{
|
||||||
|
switch (edge)
|
||||||
|
{
|
||||||
|
case WMSZ_TOP:
|
||||||
|
case WMSZ_TOPLEFT:
|
||||||
|
case WMSZ_TOPRIGHT:
|
||||||
|
prc->top = prc->bottom - minH; // 锚定下侧,回推上侧(上边被拖)
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
prc->bottom = prc->top + minH; // 锚定上侧,回推下侧(下边被拖)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Window::Window(int width, int height, int mode, COLORREF bkcloc, std::string headline)
|
// ---------------- 构造 / 析构 ----------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造:初始化当前尺寸、待应用尺寸、最小客户区尺寸与 EasyX 模式。
|
||||||
|
* 注意:样式设置与子类化放在 draw() 内第一次绘制时完成。
|
||||||
|
*/
|
||||||
|
Window::Window(int w, int h, int mode)
|
||||||
{
|
{
|
||||||
this->width = width;
|
localwidth = minClientW = pendingW = width = w;
|
||||||
this->height = height;
|
localheight = minClientH = pendingH = height = h;
|
||||||
this->windowMode = mode;
|
windowMode = mode;
|
||||||
this->wBkcolor = bkcloc;
|
}
|
||||||
this->headline = headline;
|
|
||||||
|
Window::Window(int w, int h, int mode, COLORREF bk)
|
||||||
|
{
|
||||||
|
localwidth = minClientW = pendingW = width = w;
|
||||||
|
localheight = minClientH = pendingH = height = h;
|
||||||
|
windowMode = mode;
|
||||||
|
wBkcolor = bk;
|
||||||
|
}
|
||||||
|
|
||||||
|
Window::Window(int w, int h, int mode, COLORREF bk, std::string title)
|
||||||
|
{
|
||||||
|
localwidth = minClientW = pendingW = width = w;
|
||||||
|
localheight = minClientH = pendingH = height = h;
|
||||||
|
windowMode = mode;
|
||||||
|
wBkcolor = bk;
|
||||||
|
headline = std::move(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
Window::~Window()
|
Window::~Window()
|
||||||
{
|
{
|
||||||
if (background)
|
// 析构:释放背景图对象并关闭 EasyX 图形环境
|
||||||
delete background;
|
if (background) delete background;
|
||||||
background = nullptr;
|
background = nullptr;
|
||||||
closegraph(); // 确保关闭图形上下文
|
closegraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------- 原生消息钩子----------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WndProcThun
|
||||||
|
* 作用:替换 EasyX 的窗口过程,接管关键消息。
|
||||||
|
* 关键处理:
|
||||||
|
* - WM_ERASEBKGND:返回 1,交由自绘清屏,避免系统擦背景造成闪烁。
|
||||||
|
* - WM_ENTERSIZEMOVE:开始拉伸 → isSizing=true 且 WM_SETREDRAW(FALSE) 冻结重绘。
|
||||||
|
* - WM_SIZING:拉伸中 → 仅做“最小尺寸夹紧”(按被拖边回推),不回滚、不绘制。
|
||||||
|
* - WM_EXITSIZEMOVE:结束拉伸 → 读取最终客户区尺寸 → 标记 needResizeDirty,解冻并刷新。
|
||||||
|
* - WM_GETMINMAXINFO:提供系统最小轨迹限制(四边一致)。
|
||||||
|
*/
|
||||||
|
LRESULT CALLBACK Window::WndProcThunk(HWND h, UINT m, WPARAM w, LPARAM l)
|
||||||
|
{
|
||||||
|
auto* self = reinterpret_cast<Window*>(GetWindowLongPtr(h, GWLP_USERDATA));
|
||||||
|
if (!self)
|
||||||
|
{
|
||||||
|
return DefWindowProc(h, m, w, l);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键点①:禁止系统擦背景,避免和我们自己的清屏/双缓冲打架造成闪烁
|
||||||
|
if (m == WM_ERASEBKGND)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键点②:拉伸开始 → 冻结重绘(系统调整窗口矩形时不触发即时重绘,防止抖)
|
||||||
|
if (m == WM_ENTERSIZEMOVE)
|
||||||
|
{
|
||||||
|
self->isSizing = true;
|
||||||
|
SendMessage(h, WM_SETREDRAW, FALSE, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键点③:拉伸中 → 仅执行“最小尺寸夹紧”,不做对齐/节流/回滚,保持系统自然流畅
|
||||||
|
if (m == WM_SIZING)
|
||||||
|
{
|
||||||
|
RECT* prc = reinterpret_cast<RECT*>(l);
|
||||||
|
|
||||||
|
// “尺寸异常值”快速过滤:仅保护极端值,不影响正常拖动
|
||||||
|
int currentWidth = prc->right - prc->left;
|
||||||
|
int currentHeight = prc->bottom - prc->top;
|
||||||
|
if (currentWidth < 0 || currentHeight < 0 || currentWidth > 10000 || currentHeight > 10000)
|
||||||
|
{
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyMinSizeOnSizing(prc, w, h, self->minClientW, self->minClientH);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键点④:拉伸结束 → 解冻重绘 + 统一收口(记录最终尺寸 -> 标记 needResizeDirty)
|
||||||
|
if (m == WM_EXITSIZEMOVE)
|
||||||
|
{
|
||||||
|
self->isSizing = false;
|
||||||
|
|
||||||
|
RECT rc; GetClientRect(h, &rc);
|
||||||
|
const int aw = rc.right - rc.left;
|
||||||
|
const int ah = rc.bottom - rc.top;
|
||||||
|
if (aw >= self->minClientW && ah >= self->minClientH && aw <= 10000 && ah <= 10000)
|
||||||
|
{
|
||||||
|
self->pendingW = aw;
|
||||||
|
self->pendingH = ah;
|
||||||
|
self->needResizeDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 结束拉伸后不立即执行重绘,待事件循环统一收口。
|
||||||
|
// 立即解冻重绘标志,同时标记区域为有效,避免触发额外 WM_PAINT。
|
||||||
|
SendMessage(h, WM_SETREDRAW, TRUE, 0);
|
||||||
|
ValidateRect(h, nullptr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键点⑤:系统级最小轨迹限制(与 WM_SIZING 的夹紧互相配合)
|
||||||
|
if (m == WM_GETMINMAXINFO)
|
||||||
|
{
|
||||||
|
auto* mmi = reinterpret_cast<MINMAXINFO*>(l);
|
||||||
|
RECT rc{ 0, 0, self->minClientW, self->minClientH };
|
||||||
|
DWORD style = GetWindowLong(h, GWL_STYLE);
|
||||||
|
DWORD ex = GetWindowLong(h, GWL_EXSTYLE);
|
||||||
|
// 若后续添加菜单,请把第三个参数改为 HasMenu(h)
|
||||||
|
AdjustWindowRectEx(&rc, style, FALSE, ex);
|
||||||
|
mmi->ptMinTrackSize.x = rc.right - rc.left;
|
||||||
|
mmi->ptMinTrackSize.y = rc.bottom - rc.top;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其它消息:回落到旧过程
|
||||||
|
return self->oldWndProc ? CallWindowProc(self->oldWndProc, h, m, w, l)
|
||||||
|
: DefWindowProc(h, m, w, l);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- 绘制 ----------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* draw()
|
||||||
|
* 作用:首次初始化 EasyX 窗口与子类化过程;应用可拉伸样式;清屏并批量绘制。
|
||||||
|
* 关键步骤:
|
||||||
|
* 1)initgraph 拿到 hWnd;
|
||||||
|
* 2)SetWindowLongPtr 子类化到 WndProcThunk(只做一次);
|
||||||
|
* 3)ApplyResizableStyle 设置 WS_THICKFRAME/裁剪/(可选)合成双缓冲;
|
||||||
|
* 4)去掉类样式 CS_HREDRAW/CS_VREDRAW,避免全窗无效化引发闪屏;
|
||||||
|
* 5)清屏 + Begin/EndBatchDraw 批量绘制控件&对话框。
|
||||||
|
*/
|
||||||
void Window::draw()
|
void Window::draw()
|
||||||
{
|
{
|
||||||
hWnd = initgraph(width, height, windowMode);
|
if (!hWnd)
|
||||||
SetWindowText(hWnd,headline.c_str());
|
{
|
||||||
setbkcolor(wBkcolor);
|
hWnd = initgraph(width, height, windowMode);
|
||||||
cleardevice();
|
}
|
||||||
BeginBatchDraw(); // 开始批量绘制
|
|
||||||
// 绘制所有子控件
|
|
||||||
for (auto& control : controls)
|
|
||||||
control->draw();
|
|
||||||
EndBatchDraw(); // 结束批量绘制
|
|
||||||
}
|
|
||||||
// 使用背景图片绘制窗口
|
|
||||||
// @参数 pImgFile: 图片文件路径,支持常见图片格式
|
|
||||||
// @备注: 会拉伸图片以适应窗口尺寸
|
|
||||||
void Window::draw(std::string pImgFile)
|
|
||||||
{
|
|
||||||
this->background = new IMAGE(width, height);
|
|
||||||
hWnd = initgraph(width, height, windowMode);
|
|
||||||
SetWindowText(hWnd, headline.c_str());
|
|
||||||
loadimage(background, pImgFile.c_str(), width, height, true);
|
|
||||||
putimage(0,0, background);
|
|
||||||
|
|
||||||
// 绘制所有子控件
|
// 子类化:让我们的 WndProcThunk 接管窗口消息(仅执行一次)
|
||||||
BeginBatchDraw(); // 开始批量绘制
|
if (!procHooked)
|
||||||
for (auto& control : controls)
|
{
|
||||||
control->draw();
|
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)this);
|
||||||
EndBatchDraw(); // 结束批量绘制
|
oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)&Window::WndProcThunk);
|
||||||
|
procHooked = (oldWndProc != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyResizableStyle(hWnd, useComposited);
|
||||||
|
|
||||||
|
// 关闭类样式的整窗重绘标志(减少尺寸变化时的整窗 redraw)
|
||||||
|
LONG_PTR cls = GetClassLongPtr(hWnd, GCL_STYLE);
|
||||||
|
cls &= ~(CS_HREDRAW | CS_VREDRAW);
|
||||||
|
SetClassLongPtr(hWnd, GCL_STYLE, cls);
|
||||||
|
|
||||||
|
setbkcolor(wBkcolor);
|
||||||
|
cleardevice();
|
||||||
|
|
||||||
|
BeginBatchDraw();
|
||||||
|
for (auto& c : controls)
|
||||||
|
{
|
||||||
|
c->draw();
|
||||||
|
}
|
||||||
|
for (auto& d : dialogs)
|
||||||
|
{
|
||||||
|
d->draw();
|
||||||
|
}
|
||||||
|
EndBatchDraw();
|
||||||
}
|
}
|
||||||
// 运行主事件循环,处理用户输入和窗口消息
|
|
||||||
// 此方法会阻塞直到窗口关闭
|
/**
|
||||||
void Window::runEventLoop()
|
* draw(imagePath)
|
||||||
|
* 作用:在 draw() 的基础上加载并绘制背景图;其它流程完全一致。
|
||||||
|
* 注意:这里按当前窗口客户区大小加载背景图(loadimage 的 w/h),保证铺满。
|
||||||
|
*/
|
||||||
|
void Window::draw(std::string imagePath)
|
||||||
{
|
{
|
||||||
ExMessage msg;
|
if (!hWnd)
|
||||||
bool running = true;
|
{
|
||||||
while (running) {
|
hWnd = initgraph(width, height, windowMode);
|
||||||
msg = getmessage(EX_MOUSE | EX_KEY);
|
}
|
||||||
if (msg.message == WM_CLOSE) {
|
|
||||||
running = false;
|
if (!procHooked)
|
||||||
continue;
|
{
|
||||||
|
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)this);
|
||||||
|
oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)&Window::WndProcThunk);
|
||||||
|
procHooked = (oldWndProc != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bkImageFile = std::move(imagePath);
|
||||||
|
if (!headline.empty())
|
||||||
|
{
|
||||||
|
SetWindowText(hWnd, headline.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyResizableStyle(hWnd, useComposited);
|
||||||
|
|
||||||
|
LONG_PTR cls = GetClassLongPtr(hWnd, GCL_STYLE);
|
||||||
|
cls &= ~(CS_HREDRAW | CS_VREDRAW);
|
||||||
|
SetClassLongPtr(hWnd, GCL_STYLE, cls);
|
||||||
|
|
||||||
|
if (background)
|
||||||
|
{
|
||||||
|
delete background;
|
||||||
|
background = nullptr;
|
||||||
|
}
|
||||||
|
background = new IMAGE;
|
||||||
|
loadimage(background, bkImageFile.c_str(), width, height, true);
|
||||||
|
putimage(0, 0, background);
|
||||||
|
|
||||||
|
BeginBatchDraw();
|
||||||
|
for (auto& c : controls)
|
||||||
|
{
|
||||||
|
c->setDirty(true);
|
||||||
|
c->draw();
|
||||||
|
}
|
||||||
|
for (auto& d : dialogs)
|
||||||
|
{
|
||||||
|
d->draw();
|
||||||
|
}
|
||||||
|
EndBatchDraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- 事件循环 ----------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* runEventLoop()
|
||||||
|
* 作用:驱动输入/窗口消息;集中处理“统一收口重绘”。
|
||||||
|
* 关键策略:
|
||||||
|
* - WM_SIZE:始终更新 pendingW/H(即使在拉伸中也只记录不立即绘制);
|
||||||
|
* - needResizeDirty:当尺寸确实变化时置位,随后在循环尾进行一次性重绘;
|
||||||
|
* - 非模态对话框优先消费事件(顶层从后往前);再交给普通控件。
|
||||||
|
*/
|
||||||
|
int Window::runEventLoop()
|
||||||
|
{
|
||||||
|
ExMessage msg;
|
||||||
|
bool running = true;
|
||||||
|
|
||||||
|
// 说明:统一使用 needResizeDirty 作为“收口重绘”的唯一标志位
|
||||||
|
// 不再引入额外 pendingResize 等状态,避免分叉导致状态不一致。
|
||||||
|
while (running)
|
||||||
|
{
|
||||||
|
bool consume = false;
|
||||||
|
|
||||||
|
if (peekmessage(&msg, EX_MOUSE | EX_KEY | EX_WINDOW, true))
|
||||||
|
{
|
||||||
|
if (msg.message == WM_CLOSE)
|
||||||
|
{
|
||||||
|
running = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保险:如果 EX_WINDOW 转译了 GETMINMAXINFO,同样按最小客户区折算处理
|
||||||
|
if (msg.message == WM_GETMINMAXINFO)
|
||||||
|
{
|
||||||
|
auto* mmi = reinterpret_cast<MINMAXINFO*>(msg.lParam);
|
||||||
|
RECT rc{ 0, 0, minClientW, minClientH };
|
||||||
|
DWORD style = GetWindowLong(hWnd, GWL_STYLE);
|
||||||
|
DWORD ex = GetWindowLong(hWnd, GWL_EXSTYLE);
|
||||||
|
AdjustWindowRectEx(&rc, style, FALSE, ex);
|
||||||
|
mmi->ptMinTrackSize.x = rc.right - rc.left;
|
||||||
|
mmi->ptMinTrackSize.y = rc.bottom - rc.top;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键点⑥:WM_SIZE 只记录新尺寸;若非拉伸阶段则立即置位 needResizeDirty
|
||||||
|
if (msg.message == WM_SIZE && msg.wParam != SIZE_MINIMIZED)
|
||||||
|
{
|
||||||
|
const int nw = LOWORD(msg.lParam);
|
||||||
|
const int nh = HIWORD(msg.lParam);
|
||||||
|
|
||||||
|
// 基本合法性校验(不小于最小值、不过大)
|
||||||
|
if (nw >= minClientW && nh >= minClientH && nw <= 10000 && nh <= 10000)
|
||||||
|
{
|
||||||
|
if (nw != width || nh != height)
|
||||||
|
{
|
||||||
|
pendingW = nw;
|
||||||
|
pendingH = nh;
|
||||||
|
// 在“非拉伸阶段”的 WM_SIZE(例如最大化/还原/程序化调整)直接触发收口
|
||||||
|
needResizeDirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输入优先:先给顶层“非模态对话框”,再传给普通控件
|
||||||
|
for (auto it = dialogs.rbegin(); it != dialogs.rend(); ++it)
|
||||||
|
{
|
||||||
|
auto& d = *it;
|
||||||
|
if (d->IsVisible() && !d->model())
|
||||||
|
{
|
||||||
|
consume = d->handleEvent(msg);
|
||||||
|
}
|
||||||
|
if (consume) break;
|
||||||
|
}
|
||||||
|
if (!consume)
|
||||||
|
{
|
||||||
|
for (auto& c : controls)
|
||||||
|
{
|
||||||
|
consume = c->handleEvent(msg);
|
||||||
|
if (consume) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//如果有对话框打开或者关闭强制重绘
|
||||||
|
bool needredraw = false;
|
||||||
|
for (auto& d : dialogs)
|
||||||
|
{
|
||||||
|
needredraw = d->IsVisible();
|
||||||
|
if (needredraw)break;
|
||||||
}
|
}
|
||||||
for (auto& c : controls)
|
if (needredraw || dialogClose)
|
||||||
c->handleEvent(msg);
|
{
|
||||||
flushmessage(EX_MOUSE |EX_KEY |EX_CHAR|EX_WINDOW);
|
// 对话框关闭后,需要手动合成一个鼠标移动消息并分发给所有普通控件,
|
||||||
Sleep(10);
|
// 以便它们能及时更新悬停状态(hover),否则悬停状态可能保持错误状态。
|
||||||
}
|
// 先把当前鼠标位置转换为客户区坐标,并合成一次 WM_MOUSEMOVE,先分发给控件更新 hover 状态
|
||||||
|
POINT pt;
|
||||||
|
if (GetCursorPos(&pt))
|
||||||
|
{
|
||||||
|
ScreenToClient(this->hWnd, &pt);
|
||||||
|
ExMessage mm;
|
||||||
|
mm.message = WM_MOUSEMOVE;
|
||||||
|
mm.x = (short)pt.x;
|
||||||
|
mm.y = (short)pt.y;
|
||||||
|
// 只分发给 window 层控件(因为 dialog 已经关闭或即将关闭)
|
||||||
|
for (auto& c : controls)
|
||||||
|
c->handleEvent(mm);
|
||||||
|
}
|
||||||
|
|
||||||
|
BeginBatchDraw();
|
||||||
|
// 先绘制普通控件
|
||||||
|
for (auto& c : controls)
|
||||||
|
c->draw();
|
||||||
|
// 然后绘制对话框(确保对话框在最上层)
|
||||||
|
for (auto& d : dialogs)
|
||||||
|
{
|
||||||
|
if (!d->model() && d->IsVisible())
|
||||||
|
d->setDirty(true);
|
||||||
|
d->draw();
|
||||||
|
}
|
||||||
|
EndBatchDraw();
|
||||||
|
needredraw = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
// —— 统一收口(needResizeDirty 为真时执行一次性重绘)——
|
||||||
|
if (needResizeDirty)
|
||||||
|
{
|
||||||
|
// 以“实际客户区尺寸”为准,防止 pending 与真实尺寸出现偏差
|
||||||
|
RECT clientRect;
|
||||||
|
GetClientRect(hWnd, &clientRect);
|
||||||
|
int actualWidth = clientRect.right - clientRect.left;
|
||||||
|
int actualHeight = clientRect.bottom - clientRect.top;
|
||||||
|
|
||||||
|
const int finalW = (std::max)(minClientW, actualWidth);
|
||||||
|
const int finalH = (std::max)(minClientH, actualHeight);
|
||||||
|
|
||||||
|
// 变化过大/异常场景保护
|
||||||
|
if (finalW != width || finalH != height)
|
||||||
|
{
|
||||||
|
if (abs(finalW - width) > 1000 || abs(finalH - height) > 1000)
|
||||||
|
{
|
||||||
|
// 认为是异常帧,跳过本次(不改变任何状态)
|
||||||
|
needResizeDirty = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 再次冻结窗口更新,保证批量绘制的原子性
|
||||||
|
SendMessage(hWnd, WM_SETREDRAW, FALSE, 0);
|
||||||
|
|
||||||
|
BeginBatchDraw();
|
||||||
|
|
||||||
|
// 调整底层画布尺寸
|
||||||
|
if (finalW != width || finalH != height)
|
||||||
|
{
|
||||||
|
Resize(nullptr, finalW, finalH);
|
||||||
|
|
||||||
|
// 重取一次实际客户区尺寸做确认
|
||||||
|
GetClientRect(hWnd, &clientRect);
|
||||||
|
int confirmedWidth = clientRect.right - clientRect.left;
|
||||||
|
int confirmedHeight = clientRect.bottom - clientRect.top;
|
||||||
|
|
||||||
|
int renderWidth = confirmedWidth;
|
||||||
|
int renderHeight = confirmedHeight;
|
||||||
|
|
||||||
|
// 背景:若设置了背景图则重载并铺满;否则清屏为纯色
|
||||||
|
if (background && !bkImageFile.empty())
|
||||||
|
{
|
||||||
|
delete background;
|
||||||
|
background = new IMAGE;
|
||||||
|
loadimage(background, bkImageFile.c_str(), renderWidth, renderHeight, true);
|
||||||
|
putimage(0, 0, background);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setbkcolor(wBkcolor);
|
||||||
|
cleardevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最终提交“当前已应用尺寸”(用于外部查询/下次比较)
|
||||||
|
width = renderWidth;
|
||||||
|
height = renderHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量通知控件“窗口尺寸变化”,并标记重绘
|
||||||
|
for (auto& c : controls)
|
||||||
|
{
|
||||||
|
adaptiveLayout(c,finalH,finalW);
|
||||||
|
c->onWindowResize();
|
||||||
|
}
|
||||||
|
for (auto& d : dialogs)
|
||||||
|
{
|
||||||
|
if (auto dd = dynamic_cast<Dialog*>(d.get()))
|
||||||
|
{
|
||||||
|
dd->setDirty(true);
|
||||||
|
dd->setInitialization(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统一批量绘制
|
||||||
|
for (auto& c : controls) c->draw();
|
||||||
|
for (auto& d : dialogs) d->draw();
|
||||||
|
|
||||||
|
EndBatchDraw();
|
||||||
|
|
||||||
|
// 解冻后标记区域有效,避免系统再次触发 WM_PAINT 覆盖自绘内容。
|
||||||
|
SendMessage(hWnd, WM_SETREDRAW, TRUE, 0);
|
||||||
|
ValidateRect(hWnd, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
needResizeDirty = false; // 收口完成,清标志
|
||||||
|
}
|
||||||
|
|
||||||
|
// 轻微睡眠,削峰填谷(不阻塞拖拽体验)
|
||||||
|
Sleep(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------- 其余接口 ----------------
|
||||||
|
|
||||||
void Window::setBkImage(std::string pImgFile)
|
void Window::setBkImage(std::string pImgFile)
|
||||||
{
|
{
|
||||||
if(nullptr == background)
|
// 更换背景图:立即加载并绘制一次;同时将所有控件标 dirty 并重绘
|
||||||
this->background = new IMAGE;
|
if (background) delete background;
|
||||||
else
|
background = new IMAGE;
|
||||||
delete background;
|
bkImageFile = std::move(pImgFile);
|
||||||
this->background = new IMAGE;
|
|
||||||
loadimage(background, pImgFile.c_str(), width, height, true);
|
loadimage(background, bkImageFile.c_str(), width, height, true);
|
||||||
putimage(0, 0, background);
|
putimage(0, 0, background);
|
||||||
|
|
||||||
|
BeginBatchDraw();
|
||||||
|
for (auto& c : controls)
|
||||||
|
{
|
||||||
|
c->setDirty(true);
|
||||||
|
c->draw();
|
||||||
|
}
|
||||||
|
for (auto& d : dialogs)
|
||||||
|
{
|
||||||
|
d->setDirty(true);
|
||||||
|
d->draw();
|
||||||
|
}
|
||||||
|
EndBatchDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::setBkcolor(COLORREF c)
|
void Window::setBkcolor(COLORREF c)
|
||||||
{
|
{
|
||||||
wBkcolor = c;
|
// 更换纯色背景:立即清屏并批量重绘控件/对话框
|
||||||
|
wBkcolor = c;
|
||||||
|
setbkcolor(wBkcolor);
|
||||||
|
cleardevice();
|
||||||
|
|
||||||
|
BeginBatchDraw();
|
||||||
|
for (auto& c : controls)
|
||||||
|
{
|
||||||
|
c->setDirty(true);
|
||||||
|
c->draw();
|
||||||
|
}
|
||||||
|
for (auto& d : dialogs)
|
||||||
|
{
|
||||||
|
d->setDirty(true);
|
||||||
|
d->draw();
|
||||||
|
}
|
||||||
|
EndBatchDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::setHeadline(std::string headline)
|
void Window::setHeadline(std::string title)
|
||||||
{
|
{
|
||||||
this->headline = headline;
|
// 设置窗口标题(仅改文本,不触发重绘)
|
||||||
SetWindowText(this->hWnd, headline.c_str());
|
headline = std::move(title);
|
||||||
|
if (hWnd)
|
||||||
|
SetWindowText(hWnd, headline.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::addControl(std::unique_ptr<Control> control)
|
void Window::addControl(std::unique_ptr<Control> control)
|
||||||
{
|
{
|
||||||
this->controls.push_back(std::move(control));
|
// 新增控件:仅加入管理容器,具体绘制在 draw()/收口时统一进行
|
||||||
|
controls.push_back(std::move(control));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Window::addDialog(std::unique_ptr<Control> dlg)
|
||||||
|
{
|
||||||
|
// 新增非模态对话框:管理顺序决定事件优先级(顶层从后往前)
|
||||||
|
dialogs.push_back(std::move(dlg));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Window::hasNonModalDialogWithCaption(const std::string& caption, const std::string& message) const
|
||||||
|
{
|
||||||
|
// 查询是否存在“可见且非模态”的对话框(用于避免重复弹)
|
||||||
|
for (const auto& dptr : dialogs)
|
||||||
|
{
|
||||||
|
if (!dptr) continue;
|
||||||
|
if (auto* d = dynamic_cast<Dialog*>(dptr.get()))
|
||||||
|
{
|
||||||
|
if (d->IsVisible() && !d->model() && d->GetCaption() == caption && d->GetText() == message)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HWND Window::getHwnd() const
|
||||||
|
{
|
||||||
|
return hWnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Window::getWidth() const
|
||||||
|
{
|
||||||
|
// 注意:这里返回 pendingW
|
||||||
|
// 表示“最近一次收到的尺寸”(可能尚未应用到画布,最终以收口时的 width 为准)
|
||||||
|
return pendingW;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Window::getHeight() const
|
||||||
|
{
|
||||||
|
// 同上,返回 pendingH(与 getWidth 对应)
|
||||||
|
return pendingH;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Window::getHeadline() const
|
||||||
|
{
|
||||||
|
return headline;
|
||||||
|
}
|
||||||
|
|
||||||
|
COLORREF Window::getBkcolor() const
|
||||||
|
{
|
||||||
|
return wBkcolor;
|
||||||
|
}
|
||||||
|
|
||||||
|
IMAGE* Window::getBkImage() const
|
||||||
|
{
|
||||||
|
return background;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Window::getBkImageFile() const
|
||||||
|
{
|
||||||
|
return bkImageFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<Control>>& Window::getControls()
|
||||||
|
{
|
||||||
|
return controls;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::processWindowMessage(const ExMessage& msg)
|
||||||
|
{
|
||||||
|
if (msg.message == WM_SIZE && msg.wParam != SIZE_MINIMIZED)
|
||||||
|
{
|
||||||
|
const int nw = LOWORD(msg.lParam);
|
||||||
|
const int nh = HIWORD(msg.lParam);
|
||||||
|
if (nw >= minClientW && nh >= minClientH && nw <= 10000 && nh <= 10000)
|
||||||
|
{
|
||||||
|
if (nw != width || nh != height)
|
||||||
|
{
|
||||||
|
pendingW = nw;
|
||||||
|
pendingH = nh;
|
||||||
|
needResizeDirty = true; // 统一由 pumpResizeIfNeeded 来收口
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::pumpResizeIfNeeded()
|
||||||
|
{
|
||||||
|
if (!needResizeDirty) return;
|
||||||
|
|
||||||
|
RECT rc; GetClientRect(hWnd, &rc);
|
||||||
|
const int finalW = max(minClientW, rc.right - rc.left);
|
||||||
|
const int finalH = max(minClientH, rc.bottom - rc.top);
|
||||||
|
if (finalW == width && finalH == height) { needResizeDirty = false; return; }
|
||||||
|
|
||||||
|
SendMessage(hWnd, WM_SETREDRAW, FALSE, 0);
|
||||||
|
BeginBatchDraw();
|
||||||
|
|
||||||
|
// Resize + 背景
|
||||||
|
Resize(nullptr, finalW, finalH);
|
||||||
|
GetClientRect(hWnd, &rc);
|
||||||
|
if (background && !bkImageFile.empty())
|
||||||
|
{
|
||||||
|
delete background; background = new IMAGE;
|
||||||
|
loadimage(background, bkImageFile.c_str(), rc.right - rc.left, rc.bottom - rc.top, true);
|
||||||
|
putimage(0, 0, background);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setbkcolor(wBkcolor);
|
||||||
|
cleardevice();
|
||||||
|
|
||||||
|
}
|
||||||
|
width = rc.right - rc.left; height = rc.bottom - rc.top;
|
||||||
|
|
||||||
|
// 通知控件/对话框
|
||||||
|
for (auto& c : controls)
|
||||||
|
{
|
||||||
|
adaptiveLayout(c, finalH, finalW);
|
||||||
|
c->onWindowResize();
|
||||||
|
}
|
||||||
|
for (auto& d : dialogs)
|
||||||
|
if (auto* dd = dynamic_cast<Dialog*>(d.get()))
|
||||||
|
dd->setInitialization(true); // 强制对话框在新尺寸下重建布局/快照
|
||||||
|
|
||||||
|
// 重绘
|
||||||
|
for (auto& c : controls) c->draw();
|
||||||
|
for (auto& d : dialogs) d->draw();
|
||||||
|
|
||||||
|
EndBatchDraw();
|
||||||
|
SendMessage(hWnd, WM_SETREDRAW, TRUE, 0);
|
||||||
|
|
||||||
|
// 原实现在此调用 InvalidateRect 导致系统再次发送 WM_PAINT,从而重复绘制,
|
||||||
|
// 这里改为 ValidateRect:直接标记区域为有效,通知系统我们已完成绘制,不必再触发 WM_PAINT。
|
||||||
|
// 这样可以避免收口阶段的绘制与系统重绘叠加造成顺序错乱。
|
||||||
|
ValidateRect(hWnd, nullptr);
|
||||||
|
|
||||||
|
needResizeDirty = false;
|
||||||
|
}
|
||||||
|
void Window::scheduleResizeFromModal(int w, int h)
|
||||||
|
{
|
||||||
|
if (w < minClientW) w = minClientW;
|
||||||
|
if (h < minClientH) h = minClientH;
|
||||||
|
if (w > 10000) w = 10000;
|
||||||
|
if (h > 10000) h = 10000;
|
||||||
|
|
||||||
|
if (w != width || h != height)
|
||||||
|
{
|
||||||
|
pendingW = w;
|
||||||
|
pendingH = h;
|
||||||
|
needResizeDirty = true; // 交给 pumpResizeIfNeeded 做统一收口+重绘
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::adaptiveLayout(std::unique_ptr<Control>& c, const int finalH, const int finalW)
|
||||||
|
{
|
||||||
|
int origParentW = this->localwidth;
|
||||||
|
int origParentH = this->localheight;
|
||||||
|
if (c->getLayoutMode() == StellarX::LayoutMode::AnchorToEdges)
|
||||||
|
{
|
||||||
|
if ((StellarX::Anchor::Left == c->getAnchor_1() && StellarX::Anchor::Right == c->getAnchor_2())
|
||||||
|
|| (StellarX::Anchor::Right == c->getAnchor_1() && StellarX::Anchor::Left == c->getAnchor_2()))
|
||||||
|
{
|
||||||
|
int origRightDist = origParentW - (c->getLocalX() + c->getLocalWidth());
|
||||||
|
int newWidth = finalW - c->getLocalX() - origRightDist;
|
||||||
|
c->setWidth(newWidth);
|
||||||
|
// 左侧距离固定,ctrl->x 保持为 localx 相对窗口左侧(父容器为窗口,偏移0)
|
||||||
|
c->setX(c->getLocalX());
|
||||||
|
}
|
||||||
|
else if ((StellarX::Anchor::Left == c->getAnchor_1() && StellarX::Anchor::NoAnchor == c->getAnchor_2())
|
||||||
|
|| (StellarX::Anchor::NoAnchor == c->getAnchor_1() && StellarX::Anchor::Left == c->getAnchor_2())
|
||||||
|
|| (StellarX::Anchor::Left == c->getAnchor_1() && StellarX::Anchor::Left == c->getAnchor_2()))
|
||||||
|
{
|
||||||
|
// 仅左锚定:宽度固定不变
|
||||||
|
c->setX(c->getLocalX());
|
||||||
|
c->setWidth(c->getLocalWidth());
|
||||||
|
}
|
||||||
|
else if ((StellarX::Anchor::Right == c->getAnchor_1() && StellarX::Anchor::NoAnchor == c->getAnchor_2())
|
||||||
|
|| (StellarX::Anchor::NoAnchor == c->getAnchor_1() && StellarX::Anchor::Right == c->getAnchor_2())
|
||||||
|
|| (StellarX::Anchor::Right == c->getAnchor_1() && StellarX::Anchor::Right == c->getAnchor_2()))
|
||||||
|
{
|
||||||
|
int origRightDist = origParentW - (c->getLocalX() + c->getLocalWidth());
|
||||||
|
c->setWidth(c->getLocalWidth()); // 宽度不变
|
||||||
|
c->setX(finalW - origRightDist - c->getWidth());
|
||||||
|
}
|
||||||
|
else if (StellarX::Anchor::NoAnchor == c->getAnchor_1() && StellarX::Anchor::NoAnchor == c->getAnchor_2())
|
||||||
|
{
|
||||||
|
c->setX(c->getLocalX());
|
||||||
|
c->setWidth(c->getLocalWidth());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((StellarX::Anchor::Top == c->getAnchor_1() && StellarX::Anchor::Bottom == c->getAnchor_2())
|
||||||
|
|| (StellarX::Anchor::Bottom == c->getAnchor_1() && StellarX::Anchor::Top == c->getAnchor_2()))
|
||||||
|
{
|
||||||
|
// 上下锚定:高度随窗口变化
|
||||||
|
int origBottomDist = origParentH - (c->getLocalY() + c->getLocalHeight());
|
||||||
|
int newHeight = finalH - c->getLocalY() - origBottomDist;
|
||||||
|
c->setHeight(newHeight);
|
||||||
|
c->setY(c->getLocalY());
|
||||||
|
}
|
||||||
|
else if ((StellarX::Anchor::Top == c->getAnchor_1() && StellarX::Anchor::NoAnchor == c->getAnchor_2())
|
||||||
|
|| (StellarX::Anchor::NoAnchor == c->getAnchor_1() && StellarX::Anchor::Top == c->getAnchor_2())
|
||||||
|
|| (StellarX::Anchor::Top == c->getAnchor_1() && StellarX::Anchor::Top == c->getAnchor_2()))
|
||||||
|
{
|
||||||
|
c->setY(c->getLocalY());
|
||||||
|
c->setHeight(c->getLocalHeight());
|
||||||
|
}
|
||||||
|
else if ((StellarX::Anchor::Bottom == c->getAnchor_1() && StellarX::Anchor::NoAnchor == c->getAnchor_2())
|
||||||
|
|| (StellarX::Anchor::NoAnchor == c->getAnchor_1() && StellarX::Anchor::Bottom == c->getAnchor_2())
|
||||||
|
|| (StellarX::Anchor::Bottom == c->getAnchor_1() && StellarX::Anchor::Bottom == c->getAnchor_2()))
|
||||||
|
{
|
||||||
|
int origBottomDist = origParentH - (c->getLocalY() + c->getLocalHeight());
|
||||||
|
c->setHeight(c->getLocalHeight());
|
||||||
|
c->setY(finalH - origBottomDist - c->getHeight());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 垂直无锚点:默认为顶部定位,高度固定
|
||||||
|
c->setY(c->getLocalY());
|
||||||
|
c->setHeight(c->getLocalHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
2651
控件 API 文档 (StellarX GUI Framework Controls API Documentation).md
Normal file
2651
控件 API 文档 (StellarX GUI Framework Controls API Documentation).md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user