1 Commits

Author SHA1 Message Date
霜霜
e4851e16f4 Update version 2.1.0 release date in CHANGELOG
Updated release date for version 2.1.0 and clarified features.
2025-10-27 15:00:44 +08:00
26 changed files with 2388 additions and 5110 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -0,0 +1,872 @@
# English API Documentation
[API文档](API 文档.md)
Below is the API documentation for the **StellarX GUI framework** classes and functions, in English. It covers the usage, parameters, and behavior of each primary component:
### Control Class (Abstract Base Class)
**Description:** `Control` is the abstract base class for all GUI controls. It defines common properties and interfaces such as position, size, and dirty (redraw) flags. It also implements saving and restoring of drawing state to ensure that drawing a control does not affect global graphics state. `Control` itself is not instantiated directly.
- **Key Properties:**
- `x, y`: Coordinates of the control's top-left corner.
- `width, height`: Dimensions of the control.
- `dirty`: A flag indicating whether the control needs to be redrawn.
- `show`: Visibility flag (whether the control is visible).
- `rouRectangleSize`: A `StellarX::RouRectangle` struct that stores the ellipse width and height for rounded rectangle corners (used by controls with round-rect shape).
- **Note:** `Control` internally maintains pointers (`currentFont`, etc.) to backup current drawing state (font, colors, line style) so that it can restore the state after custom drawing.
- **Main Interface Methods:**
- `virtual void draw() = 0;`
**Description:** Pure virtual function to draw the control. Each derived control class must implement its own drawing logic.
- `virtual bool handleEvent(const ExMessage& msg) = 0;`
**Description:** Pure virtual function to handle an input event message (mouse or keyboard). Returns true if the event is consumed by this control (meaning it should not propagate further).
- `void saveBackground(int x, int y, int w, int h);`
**Description:** Saves a background snapshot of the specified area `(x, y, w, h)` (in screen coordinates) where the control is drawn. Used to restore background when needed (e.g., hiding a popup).
- `void restBackground();`
**Description:** Restores the last saved background image by putting it back to the saved position. Typically used when a control is hidden or needs to erase its drawing.
- `void discardBackground();`
**Description:** Discards the currently saved background snapshot and frees its resources. Should be called when the saved background is no longer valid (e.g., after window resize) to avoid using stale snapshots.
- *Property Accessors and Mutators:*
- `int getX() const, getY() const, getWidth() const, getHeight() const`: Get the control's position and size.
- `int getRight() const, getBottom() const`: Get the coordinate of the control's right boundary (`x + width`) and bottom boundary (`y + height`).
- `void setX(int nx), setY(int ny), setWidth(int w), setHeight(int h)`: Set the control's position or size. Setting these will mark the control as dirty (need redraw).
- `void setDirty(bool d)`: Manually mark the control as needing redraw or not.
- `void setShow(bool visible)`: Set the control's visibility (`show` flag). If set to false, the control will not draw itself.
- `bool isVisible() const`: Returns the current visibility state of the control (`show` flag). **Note:** If a control is hidden, its `draw()` is typically not called.
- *Other Methods:*
- `virtual bool model() const = 0;`
**Description:** Pure virtual function to check if the control is "modal". Only dialog controls need to implement this (modal dialogs return true), other controls can ignore (return false). The window event loop uses this to prioritize modal dialog events.
- `void saveStyle(); void restoreStyle();` *(protected)*
**Description:** Saves the current global drawing style (font, colors, line style, etc.) and restores it. Controls should call `saveStyle()` at the beginning of `draw()` to backup global settings, and `restoreStyle()` at the end of `draw()` to not pollute the global state for other drawings.
### Window Class (Application Main Window)
**Description:** The `Window` class represents the main application window. It manages the window creation, message loop, and acts as the root container for all controls and dialogs. Typically, an application creates one Window instance as the main GUI container.
- **Constructors:**
- `Window(int width, int height, int mode, COLORREF bkColor = ..., std::string headline = "窗口")`
**Description:** Creates a Window with specified width and height. `mode` is the graphics mode (such as double-buffering or manual flush flags in EasyX, use NULL for default). `bkColor` is the window background color, and `headline` is the window title text. Construction does not immediately display the window; you need to call `draw()` to open it.
- **Main Methods:**
- `void draw();`
**Description:** Initializes and opens the graphics window. It uses the specified mode to create the drawing window (calls EasyX `initgraph`), sets up the window's title and background color, and then draws all added controls. Usually called once right after creating the Window object.
- `void draw(std::string pImgFile);`
**Description:** Similar to the above, but uses an image file as the window background. This loads the image and draws it scaled to the window size, then draws all child controls on top.
- `void runEventLoop();`
**Description:** Enters the main event processing loop for the window. This loop continuously retrieves user input events (`peekmessage`) and dispatches them to child controls or dialogs:
- Dialogs (non-modal ones) get first priority: for each event, it iterates over `dialogs` and if a dialog is visible (`isVisible() == true`) and not modal (`model() == false`), it calls that dialog's `handleEvent`. If any dialog consumes the event (returns true), it stops further propagation.
- If no dialog consumed the event, it then iterates through `controls` (regular controls) in order and calls each control's `handleEvent` until one returns true (meaning the event was handled).
- After handling an event (or if none present), it checks if any dialog is open or was just closed (`dialogClose` flag). If so, it forces a redraw of the entire interface to keep things updated:
- It synthesizes a `WM_MOUSEMOVE` message to update hover states (ensuring controls properly update their highlight states), then calls `draw()` on all controls and dialogs, and flushes the drawing.
- It resets `dialogClose` to false after the redraw.
- Sleeps for 10 ms at each loop iteration to prevent high CPU usage.
- The loop continues until a `WM_CLOSE` message is received (e.g., user closes the window), at which point it sets `running` false and breaks out.
- `void setBkImage(IMAGE* img);` / `void setBkImage(std::string filePath);`
**Description:** Changes the window's background image. You can pass an already loaded `IMAGE*`, or specify a file path to load. After setting, it immediately repaints the window (drawing all controls and dialogs) with the new background.
- `void setBkcolor(COLORREF c);`
**Description:** Sets the window background color and immediately clears the screen with that color (this does not remove an existing background image if one was set; it just overlays the color).
- `void setHeadline(std::string title);`
**Description:** Sets the window title text. If the window is already open, it updates the title bar via `SetWindowText` immediately.
- `void addControl(std::unique_ptr<Control> control);`
**Description:** Adds a regular control to the window. The Window maintains a list of such controls; `draw()` and `runEventLoop()` use this list for rendering and event dispatch. Ownership is transferred (control is managed by Window after adding).
- `void addDialog(std::unique_ptr<Control> dialog);`
**Description:** Adds a dialog control to the window (usually an instance of `Dialog`). Dialogs are managed separately from normal controls, with their own event and drawing handling.
- `bool hasNonModalDialogWithCaption(const std::string& caption) const;`
**Description:** Checks if there is a currently open **non-modal** dialog with the given title caption. Returns true if found. Typically used to avoid opening duplicate dialogs with the same purpose.
- **Overloaded variant:** `bool hasNonModalDialogWithCaption(const std::string& caption, const std::string& text) const;`
Additionally compares the dialog's message text. This provides a stricter duplicate check (same caption **and** same content). It is used internally by `MessageBox::showAsync` to prevent popping up the same notification multiple times.
- *Information Getters:*
- `HWND getHwnd() const;` Returns the underlying window handle (HWND from EasyX).
- `int getWidth() const, getHeight() const;` Returns the window's dimensions.
- `std::string getHeadline() const;` Returns the current window title string.
- `COLORREF getBkcolor() const;` Returns the background color.
- `IMAGE* getBkImage() const;` Returns the current background image pointer (if any).
- `std::vector<std::unique_ptr<Control>>& getControls();` Returns a reference to the list of child controls (for iteration or debugging; generally you don't modify this directly).
### Canvas Class (Container Control)
**Description:** `Canvas` is a container control that can hold child controls, grouping them with a unified background and border style, and enabling composite layouts. Canvas itself is a control (derives from `Control`), and typically used for panels or dialog surfaces.
- **Features:**
- Supports four rectangular background shapes (normal rectangle or rounded rectangle, each with border or borderless version). Set via `setShape`.
- Customizable background color (`canvasBkColor`), border color (`canvasBorderColor`), border line style (`canvasLineStyle`), and fill mode (`canvasFillMode`, e.g., solid color, hatched pattern, no fill).
- Automatically manages the lifecycle of child controls (added via `addControl`, and destroyed when Canvas is destroyed).
- When drawing, Canvas will draw its background and then iterate through its child controls to draw them. For event handling, it propagates events to children (with last-added getting first chance).
- **Important Members:**
- `std::vector<std::unique_ptr<Control>> controls;` The list of child controls.
- `StellarX::ControlShape shape;` Background shape of the container (default is RECTANGLE).
- `StellarX::FillMode canvasFillMode;` Background fill mode (default Solid color).
- `StellarX::LineStyle canvasLineStyle;` Border line style (default Solid line).
- `int canvaslinewidth;` Border line width in pixels.
- `COLORREF canvasBorderColor, canvasBkColor;` Border and background colors of the container.
- `StellarX::LayoutKind Kind;` (Reserved) Layout management type (Absolute/HBox/VBox/Grid). Not fully implemented but can be used to indicate layout strategy.
- **Note:** Canvas overrides `isVisible()` to always return false because a Canvas itself is not considered a standalone visible entity for event loop prioritization (the event loop doesn't handle Canvas directly, only its children). This doesn't affect drawing or child event dispatch.
- **Main Methods:**
- `Canvas(); Canvas(int x, int y, int width, int height);`
**Description:** Constructors to create a Canvas container at position `(x,y)` with given size.
- `void addControl(std::unique_ptr<Control> control);`
**Description:** Adds a child control to the canvas. The child control's coordinates are relative to the canvas (Canvas does not reposition children automatically; you should set the child's position accordingly before adding). After adding, the Canvas is marked dirty so it will redraw with the new content.
- `void setShape(StellarX::ControlShape shape);`
**Description:** Sets the canvas background shape. Supports `RECTANGLE`, `B_RECTANGLE` (borderless rectangle), `ROUND_RECTANGLE` (rounded corners with border), `B_ROUND_RECTANGLE` (rounded without border). If a circular/ellipse shape is passed, Canvas does not support it (it will treat it as RECTANGLE internally).
- `void setCanvasFillMode(StellarX::FillMode mode);`
**Description:** Sets the background fill mode, e.g., Solid, Null (no fill), Hatched pattern, or pattern/bitmap fill. This influences how the background is drawn (using EasyX `setfillstyle`). Default is solid fill.
- `void setBorderColor(COLORREF color);` / `void setCanvasBkColor(COLORREF color);` / `void setCanvasLineStyle(StellarX::LineStyle style);` / `void setLinewidth(int width);`
**Description:** Set the container's border color, background color, border line style, and line width, respectively. Changing any of these will mark the Canvas dirty (to be redrawn).
- `void draw() override;`
**Description:** Draws the canvas background and all its child controls. Steps:
1. If not dirty, returns immediately (no redraw needed).
2. Saves current style and sets line color, fill color, fill style, and line style based on canvas properties.
3. Draws the background shape: e.g., calls `fillrectangle` or `fillroundrect` depending on `shape`. If an unsupported shape (Circle/Ellipse) was set, it defaults to rectangle.
4. Iterates over `controls` and calls each child's `setDirty(true)` then `draw()`, ensuring each child is redrawn on this canvas.
5. Restores the style and marks itself clean (`dirty = false`).
*Note:* Canvas uses `saveStyle()`/`restoreStyle()` to preserve global drawing state, and uses EasyX drawing functions to fill the background according to its shape.
- `bool handleEvent(const ExMessage& msg) override;`
**Description:** Propagates the event to its children in reverse order (so the last added child gets the event first). It iterates `controls` from end to start and calls each child's `handleEvent(msg)`. If any child returns true (meaning it consumed the event), Canvas stops and returns true. If none consumed it, returns false.
*Use case:* This ensures that in overlapping or layered controls, the topmost (last added) gets first crack at the event, implementing a basic Z-order.
- `void clearAllControls();` *(protected)*
**Description:** Clears all child controls from the canvas, deleting them. This releases all smart pointers in the `controls` list. Typically used in destructor or when resetting the UI. In normal use, you don't call this directly (the Canvas destructor will automatically free children).
### Label Class (Static Text Label)
**Description:** `Label` displays a static text string on the UI. It supports transparent background and custom text style, but does not handle user input (no interactive events). It is lightweight and intended for captions, status messages, etc.
- **Constructors:**
- `Label(); Label(int x, int y, std::string text = "标签", COLORREF textColor = BLACK, COLORREF bkColor = WHITE);`
**Description:** Creates a Label control at `(x,y)`. You can specify initial text, text color, and background color. The default text is "标签" (label in Chinese), default text color is black, background is white.
- **Key Properties:**
- `std::string text;` The text content displayed.
- `COLORREF textColor;` The text color.
- `COLORREF textBkColor;` The background color behind the text when not transparent.
- `bool textBkDisap;` Flag indicating if the background is transparent. If true, the label is drawn with a transparent background (so whatever is behind it shows through); if false, it draws an opaque rectangle behind the text using `textBkColor`.
- `StellarX::ControlText textStyle;` The text style struct (includes font face, size, weight, etc. as well as text color).
- **Note:** Label's width and height are initially 0; usually a Label's size is determined by its text content automatically when drawn. You normally don't need to set width/height for labels.
- **Main Methods:**
- `void setTextdisap(bool transparent);`
**Description:** Sets whether the label's background is transparent. If true, when drawing, the text is rendered with `TRANSPARENT` background mode (not overwriting the background behind it). If false, the text is drawn on an opaque background colored `textBkColor`.
- `void setTextColor(COLORREF color);` / `void setTextBkColor(COLORREF color);`
**Description:** Sets the text color and background color, respectively. After setting, the label is marked dirty for redraw.
- `void setText(std::string text);`
**Description:** Changes the label's displayed text content. Marks the label dirty (so it will redraw with the new text).
- `void draw() override;`
**Description:** Draws the label's text (and background if not transparent). It:
- Calls `saveStyle()`.
- If `textBkDisap` is true, calls `setbkmode(TRANSPARENT)`; otherwise `setbkmode(OPAQUE)` and sets `bkcolor` to `textBkColor`.
- Sets the text color via `settextcolor(textColor)` and font via `settextstyle` based on `textStyle`.
- Saves the area behind where the text will be drawn using `saveBackground` (so it can restore it later if needed).
- Writes the text at `(x,y)` using `outtextxy`.
- Restores the drawing style and marks `dirty = false`.
This approach ensures that if the label text is redrawn, the background behind it is handled properly.
- `bool handleEvent(...) override;`
**Description:** Label does not handle any events; it always returns false (meaning it never consumes events).
- `void hide();`
**Description:** Hides the label. Specifically, it uses `restBackground()` to put back the saved background (erasing the text from the screen) and then `discardBackground()` to free the snapshot, and sets `dirty = false`. This is typically used when a Label is serving as a transient tooltip or overlay and needs to be removed without causing a full screen redraw.
- **Usage Scenarios:**
Label is used for static text like descriptions, titles, or status information. You can adjust `textStyle` to change the font and size (by default, the font might be "微软雅黑" with height 0 meaning default height). For example:
```
Label *status = new Label(10, 10, "Ready", RGB(0,128,0));
status->textStyle.nHeight = 20; // set font size if needed
```
If you want the label to blend into a custom background, you can do:
```
status->setTextdisap(true);
```
to make the background transparent.
### Button Class (Button Control)
**Description:** `Button` provides a clickable button control, supporting both standard push-button behavior and toggle (on/off) behavior. It handles click and hover events, and allows setting various styles (shape, colors). Buttons are one of the primary interactive controls.
- **Operating Modes:** Determined by `StellarX::ButtonMode`:
- `NORMAL` Standard push-button. Each click triggers an action but does not maintain a pressed state.
- `TOGGLE` Toggle button. Each click changes the button's state (pressed vs not pressed) and triggers different callbacks for each state.
- `DISABLED` Disabled button. It does not respond to user clicks and typically displays in a grayed-out style with strikeout text.
- The mode can be changed via `setButtonMode(ButtonMode mode)`.
- **Appearance Shape:** Determined by `StellarX::ControlShape`:
- Supports rectangle (`RECTANGLE`/`B_RECTANGLE`), rounded rectangle (`ROUND_RECTANGLE`/`B_ROUND_RECTANGLE`), circle (`CIRCLE`/`B_CIRCLE`), and ellipse (`ELLIPSE`/`B_ELLIPSE`) eight shape options in total (`B_` prefix indicates borderless). Use `setButtonShape(ControlShape shape)` to set.
- Note: When switching shapes, ensure the button's width/height are appropriate (for circle/ellipse shapes, the drawing will use width and height differently; circle uses min(width,height)/2 as radius, ellipse uses width,height as bounding box).
- The button's border will be drawn for shapes without the `B_` prefix; borderless shapes omit the border.
- **Key Configurable Properties:**
- **Colors:**
- `buttonTrueColor` color when button is in pressed state (for toggle or momentary press in normal mode).
- `buttonFalseColor` color when button is not pressed (normal default state).
- `buttonHoverColor` color when mouse is hovering over the button.
- `buttonBorderColor` border outline color.
- **Fill:**
- `buttonFillMode` fill mode for the button background (solid, hatched pattern, custom pattern, custom image).
- `buttonFillIma` pattern style for hatched fills (if `FillMode == Hatched`).
- `buttonFileIMAGE` pointer to an `IMAGE` for custom image fill (if `FillMode == DibPattern`).
- **Text:**
- `text` the text label displayed on the button.
- `textStyle` text style (font face, size, weight, etc., including text color).
- Additionally, `cutText` is used internally if the text is too long to fit; the button can automatically truncate and add "..." or a Chinese ellipsis to fit.
- **Tooltip (hover hint):**
- `tipEnabled` whether a tooltip is enabled on hover.
- `tipTextClick` tooltip text for the NORMAL mode (single-state).
- `tipTextOn` / `tipTextOff` tooltip texts for toggle mode when the button is ON or OFF.
- `tipFollowCursor` if true, tooltip appears near the cursor; if false, tooltip appears at a fixed offset below the button.
- `tipDelayMs` delay in milliseconds before tooltip appears when hovering.
- `tipOffsetX, tipOffsetY` offset of tooltip position relative to cursor or button (depending on `tipFollowCursor`).
- `tipLabel` an internal `Label` object used to display the tooltip text.
- **Rounded Corner Size:** In round-rectangle shapes, the corner radii come from the inherited `rouRectangleSize` (`ROUND_RECTANGLEwidth` and `height`). Set via `setRoundRectangleWidth(int)` and `setRoundRectangleHeight(int)`.
- **Main Methods:**
- `void setOnClickListener(const std::function<void()>&& callback);`
**Description:** Sets the callback function to be invoked when the button is clicked in NORMAL mode. For a toggle button, this callback is called every time the button is clicked (but typically you might use toggle-specific callbacks instead). The callback is executed when the user releases the mouse button over the button.
- `void setOnToggleOnListener(const std::function<void()>&& callback);`
**Description:** Sets the callback for when a toggle button is switched to the "on/pressed" state.
- `void setOnToggleOffListener(const std::function<void()>&& callback);`
**Description:** Sets the callback for when a toggle button is switched to the "off/released" state.
- `void setButtonText(const char* text)` / `void setButtonText(std::string text);`
**Description:** Changes the button's label text. Accepts a C-string or an std::string. After changing, it recalculates the text width/height (for centering and truncation logic) and marks the button dirty to redraw.
- `void setButtonBorder(COLORREF border);` / `void setButtonFalseColor(COLORREF color);`
**Description:** Sets the border color and the default (false) state fill color, respectively.
- `void setFillMode(FillMode mode);` / `void setFillPattern(FillStyle pattern);` / `void setFillImage(const std::string& path);`
**Description:** Configures the background fill of the button:
- `setFillMode` changes the fill mode (solid, hatched pattern, custom bitmap, etc).
- If setting to a hatched pattern fill, call `setFillPattern` to choose the hatch style (`StellarX::FillStyle`).
- If setting to a custom image fill, call `setFillImage` with the image file path; it will load the image (resizing to button size) and store it in `buttonFileIMAGE`.
- `void setButtonClick(bool click);`
**Description:** Programmatically sets the button's "clicked" state.
- For NORMAL mode: setting true will simulate a click it triggers the onClick callback (if any) and then immediately resets to false (not pressed).
- For TOGGLE mode: setting true or false will force the button into that state and trigger the corresponding onToggle callback if provided.
- In any case, it marks the button dirty and redraws it (ensuring visual state update).
- **Note:** If the button is disabled, this method has no effect.
- `bool isClicked() const;`
**Description:** Returns whether the button is currently in the pressed state. This is primarily meaningful for toggle buttons (true if toggled on, false if off). For a normal button, this is typically false except during the brief moment of click handling.
- Other getters such as `getButtonText()`, `getButtonMode()`, `getButtonShape()`, `getFillMode()`, `getFillPattern()`, `getFillImage()`, `getButtonBorder()`, `getButtonTextColor()`, `getButtonTextStyle()`, `getButtonWidth()`, `getButtonHeight()` provide read access to the button's corresponding properties.
- `void draw() override;`
**Description:** Draws the button's background and text according to its current state and properties:
- Chooses the fill color based on state: if disabled, uses `DISABLEDCOLOUR` (gray) and applies a strike-out to the text; otherwise, if `click` is true (pressed or toggled on) uses `buttonTrueColor`, else if `hover` is true uses `buttonHoverColor`, otherwise `buttonFalseColor`.
- Sets transparent background mode for text, sets border color and text style (color, font).
- If `needCutText` is true, calls `cutButtonText()` to possibly truncate the text with ellipsis if it doesn't fit in the button width.
- Recalculates `text_width` and `text_height` if the text content or style changed (to ensure text is centered correctly).
- Sets the fill style for background (using `buttonFillMode`, pattern or image as needed).
- Draws the shape:
- For rectangle shapes: uses `fillrectangle` or `solidrectangle`.
- For round-rectangle: uses `fillroundrect` or `solidroundrect` with `rouRectangleSize` radii.
- For circle: uses `fillcircle` or `solidcircle` with radius = min(width,height)/2.
- For ellipse: uses `fillellipse` or `solidellipse` with bounding box corners.
- Draws the text:
- It calculates the position to center the text/cutText horizontally and vertically within the button.
- If `isUseCutText` is true (meaning the original text was truncated), it draws `cutText` (with "..." or Chinese ellipsis).
- Otherwise, draws the full `text`.
- Restores style and marks dirty false after drawing.
- `bool handleEvent(const ExMessage& msg) override;`
**Description:** Handles mouse events for the button:
- If the button is hidden (`show == false`), returns false immediately.
- Tracks the previous `hover` and `click` states to detect changes.
- If the message is `WM_MOUSEMOVE`, updates `lastMouseX/Y` for tooltip positioning.
- Determines `hover` state by checking if the mouse `(msg.x, msg.y)` is within the button's shape (calls `isMouseInCircle` or `isMouseInEllipse` for those shapes, otherwise simple rectangle bounds check).
- Handling `WM_LBUTTONDOWN`: if button is enabled and mouse is over it:
- In NORMAL mode: set `click = true` (button appears pressed), mark dirty, and mark event consumed.
- In TOGGLE mode: do nothing on down (toggle action is deferred to release).
- Handling `WM_LBUTTONUP`: if button is enabled and mouse is currently over it:
- In NORMAL mode: if it was pressed (`click` was true), trigger the onClick callback (if set), then set `click = false` (release it). Mark dirty, consume event, and hide tooltip (if any). Also flush the message queue of any pending mouse/keyboard events using `flushmessage` to prevent processing a duplicate click message.
- In TOGGLE mode: flip the `click` state (`click = !click`). If it becomes true, trigger onToggleOn callback; if false, trigger onToggleOff callback. Mark dirty, consume event, update tooltip text via `refreshTooltipTextForState()`, hide tooltip, and flush message queue similarly.
- Handling `WM_MOUSEMOVE`:
- If moving outside (not hover anymore) while in NORMAL mode and the button was pressed (`click == true`), then user is dragging out: set `click = false` (cancel the press) and mark dirty (so it returns to unpressed visual).
- If hover state changed (entered or exited), mark dirty.
- Tooltip management:
- If tooltip enabled (`tipEnabled`):
- If just hovered (hover true and oldHover false): record the timestamp via `tipHoverTick` and set `tipVisible = false` (starting hover timer).
- If just exited (hover false and oldHover true): immediately call `hideTooltip()` (which hides the tooltip label if it was visible).
- If still hovering and tooltip not yet visible:
- Check if current time minus `tipHoverTick` >= `tipDelayMs`; if so:
- Set `tipVisible = true`.
- Determine tooltip position: if `tipFollowCursor` is true, `tipX = lastMouseX + tipOffsetX`, `tipY = lastMouseY + tipOffsetY`. If false, perhaps `tipX = lastMouseX` (or button center) and `tipY = y + height` (so it appears below the button).
- Set the tooltip text: if `tipUserOverride` is true (meaning user explicitly set tooltip text via `setTooltipText` or `setTooltipTextsForToggle`), then:
- For NORMAL mode: always use `tipTextClick` (explicitly set text or maybe button text by default).
- For TOGGLE mode: use `click ? tipTextOn : tipTextOff`.
- If `tipUserOverride` is false (no explicit text provided):
- If mode is TOGGLE: default behavior might be to use `tipTextOn/Off` (which could be set to default values like "On"/"Off" or left empty).
- If mode is NORMAL: possibly do nothing (the code currently only sets text in else for toggle, leaving normal with no dynamic text unless overridden).
- Position the internal `tipLabel` at (tipX, tipY), mark it dirty.
- After updating tooltip state,
- If `hover` or `click` state changed compared to old states, mark the button dirty so it will redraw to reflect highlight or press/unpress.
- If the button is dirty, call `draw()` immediately for real-time feedback.
- If tooltip is enabled and now visible (`tipVisible`), call `tipLabel.draw()` to draw the tooltip label.
- Return true if the event was handled (for example, a click or certain modal conditions), else false.
- (In summary, the event is considered consumed if a click occurred or if the modal logic swallowed it. Hover alone doesnt consume the event.)
- **Usage Notes:**
Typically, you create a Button and set its callback like:
```
auto btn = std::make_unique<Button>(50, 50, 80, 30, "OK");
btn->setOnClickListener([](){ /* handle click */ });
```
For toggle functionality:
```
btn->setButtonMode(StellarX::ButtonMode::TOGGLE);
btn->setOnToggleOnListener([](){ /* handle toggle on */ });
btn->setOnToggleOffListener([](){ /* handle toggle off */ });
```
Adjusting appearance:
```
btn->textStyle.color = RGB(255,255,255); // white text
btn->setButtonFalseColor(RGB(100,150,200)); // default background
btn->setButtonBorder(RGB(80,80,80)); // dark gray border
btn->setButtonShape(StellarX::ControlShape::RECTANGLE);
```
To provide a tooltip on hover:
```
btn->tipEnabled = true;
btn->tipTextClick = "Click to confirm"; // for normal mode
// for toggle mode:
// btn->setTooltipTextsForToggle("On state hint", "Off state hint");
```
The Button control will handle highlighting itself when hovered, pressing down on click, toggling, etc. Ensure to keep the `runEventLoop` running so these UI feedbacks happen in real time.
### TextBox Class (Single-line Text Box)
**Description:** `TextBox` is a single-line text input box, which can either allow user input or be read-only. It uses an EasyX input box for user text entry (which is a modal popup), then displays the entered text in the control. It's a simple text field primarily for small inputs like numbers or short strings.
- **Modes:** Controlled by `StellarX::TextBoxmode`:
- `INPUT_MODE` the user can click and enter text. On click, a modal input dialog appears where the user can type.
- `READONLY_MODE` the text box is for display only. Clicking it will not change the text; it might just show an alert that input is not allowed (the current implementation pops an InputBox with a message).
- Set via `setMode(TextBoxmode mode)`.
- **Appearance:**
- Supports rectangular shapes (normal or rounded, with or without border). Set with `setTextBoxShape(ControlShape)`. Circular/ellipse shapes are not logically typical for text entry and are treated as rectangle in the implementation.
- By default, a TextBox has a black border and white background. Use `setTextBoxBorder` and `setTextBoxBk` to change those.
- Text style is adjustable via `textStyle` (including font face, size, color).
- **Key Properties:**
- `std::string text;` The content string displayed in the text box.
- `TextBoxmode mode;` Current mode (input or read-only).
- `ControlShape shape;` Current border shape (default RECTANGLE or B_RECTANGLE).
- `bool click;` (Internal flag) Indicates if the text box was clicked (used to trigger the InputBox; it's set true on LButtonUp and then immediately reset after processing).
- `size_t maxCharLen;` Maximum number of characters allowed. Default is 255.
- `COLORREF textBoxBorderColor, textBoxBkColor;` Border color and background color of the text box.
- `StellarX::ControlText textStyle;` Text style for the content (e.g., can use monospace font for numeric input if desired, or change the color).
- **Main Methods:**
- `void setMode(StellarX::TextBoxmode mode);`
**Description:** Switches the text box between input mode and read-only mode. Changing mode does not clear the current text content.
- `void setMaxCharLen(size_t len);`
**Description:** Sets the maximum number of characters that can be input. If len > 0 (non-zero positive), it will enforce that limit. Input beyond this length will be truncated.
- `void setTextBoxShape(StellarX::ControlShape shape);`
**Description:** Sets the shape of the text box. Supports rectangle or rounded rectangle (with or without border). If a shape like circle/ellipse is passed, it's internally treated as rectangle (the implementation falls back to rectangle for unsupported shapes). Changing shape marks the text box dirty for redraw.
- `void setTextBoxBorder(COLORREF color);` / `void setTextBoxBk(COLORREF color);`
**Description:** Sets the border color and background color of the text box.
- `void setText(std::string text);`
**Description:** Updates the displayed text content of the text box. If the new text exceeds `maxCharLen`, it is automatically truncated to that length. Marks the text box dirty and immediately calls `draw()` to update the displayed text.
- `std::string getText() const;`
**Description:** Returns the current text content of the text box.
- `void draw() override;`
**Description:** Draws the text box background and the text:
- Saves style and sets `fillcolor` to `textBoxBkColor`, `linecolor` to `textBoxBorderColor`.
- Ensures the font height/width do not exceed the control's dimensions (if `textStyle.nHeight` is larger than `height`, it sets it equal to height; similarly for width).
- Sets the font and text color via `settextstyle` and `settextcolor` according to `textStyle`.
- Transparent background mode (`setbkmode(TRANSPARENT)` is used to avoid drawing a separate background behind text, since the background is already filled by fillrectangle).
- Calculates the pixel width and height of the text (`textwidth` and `textheight`).
- Draws the background shape:
- If shape is RECTANGLE (with border): calls `fillrectangle` (the border is then drawn by outline because linecolor is set).
- If shape is B_RECTANGLE: calls `solidrectangle` (no border outline).
- If shape is ROUND_RECTANGLE: calls `fillroundrect` using `rouRectangleSize` for corners.
- If shape is B_ROUND_RECTANGLE: calls `solidroundrect`.
- (Other shapes default to rectangle).
- Draws the text: It positions the text 10 pixels from the left inside the box (`x + 10`) and vertically centers it (`y + (height - text_h) / 2`).
- Restores the style and sets dirty false.
- **Note:** If text content is larger than the control width, it will overflow/clipped; there's no automatic horizontal scrolling or ellipsis for text box implemented. The developer should ensure the width is sufficient or the `maxCharLen` is set appropriately.
- `bool handleEvent(const ExMessage& msg) override;`
**Description:** Handles the mouse events for the text box:
- Determines if the mouse is over the text box (`hover`) using the shape logic (similar to button, but generally rectangular).
- For `WM_LBUTTONUP` when the cursor is over the text box:
- If in `INPUT_MODE`: It sets `click = true` and calls `InputBox` (EasyX's modal input dialog). The `InputBox` parameters include:
- The output buffer (here passing `LPTSTR(text.c_str())` to supply initial text).
- Max characters (`maxCharLen`).
- A title like "输入框" (Input Box).
- It passes `text.c_str()` as default content, etc.
- The returned value (the function returns nonzero if text changed, 0 if canceled, but here they capture it as `dirty`).
- After `InputBox` returns, if any input was done, the `text` variable will contain the new text (because the first parameter was the buffer referencing `text` content).
- They mark `dirty` based on InputBox result (the code sets `dirty` to whatever InputBox returned, which in EasyX indicates whether text was changed).
- They mark `consume = true` to indicate the click was handled.
- If in `READONLY_MODE`: They do not change the text. Instead, they possibly call `InputBox(NULL, maxCharLen, "输出框(输入无效!)", ... text ...)` which essentially shows the text in an output box stating input is invalid (effectively a message box).
- They keep `dirty = false` (since no visual change), set `consume = true`.
- In both cases, after handling, they flush the input messages with `flushmessage(EX_MOUSE | EX_KEY)` to clear any leftover events (similar reason to button: to avoid re-triggering).
- For other messages (like `WM_LBUTTONDOWN` or `WM_MOUSEMOVE`):
- `WM_LBUTTONDOWN` is not explicitly handled (the code simply sets `hover` and sets `consume = false` in the switch).
- `WM_MOUSEMOVE`: The code sets `hover` accordingly (and uses `consume = false`).
- After event processing:
- If `dirty` is true (text changed), it calls `draw()` to refresh the display with new text.
- If `click` was set (meaning an input happened), it resets `click = false` after handling to ensure state is consistent.
- Returns `consume` (which would be true if a click caused an InputBox or a read-only alert, false otherwise).
- Essentially, clicking the text box in input mode opens an input dialog for user to type, and clicking it in read-only mode just shows a dummy output box (so user knows it's not editable).
- **Usage Considerations:**
- Because `InputBox` is modal, it will pause the entire program's event loop until the user closes the input dialog. This simplifies input handling but means the UI doesn't update during text entry (the `runEventLoop` is blocked). For small apps and short inputs, this is acceptable, but for more advanced usage you might want to implement a custom non-blocking input field.
- Always set an appropriate `maxCharLen` if expecting certain input sizes (like numeric values of certain length) to avoid overflow of the display region.
- Use `textStyle` to set a suitable font for the input. For instance, for numeric input, you might choose a monospace font for alignment.
- Example:
```
auto field = std::make_unique<TextBox>(100, 100, 200, 30, "0");
field->setMaxCharLen(10);
field->textStyle.color = RGB(255,69,0); // orange text
field->textStyle.nHeight = 18;
// field is input mode by default. Optionally:
// field->setMode(StellarX::TextBoxmode::READONLY_MODE);
```
- After user input (in input mode), you can retrieve the text via `field->getText()`. In read-only mode, you may set the text programmatically via `setText` and it will just display.
### Dialog Class (Dialog Control)
**Description:** `Dialog` implements modal and modeless dialog windows with rich content and multiple buttons (like message boxes). It inherits from `Canvas` (so it is a container), and includes a caption, message text, and standard buttons combination. It integrates with `Window` to manage blocking input for modal dialogs.
- **Creation & Modes:**
- Constructor `Dialog(Window& parent, std::string title, std::string message = "对话框", MessageBoxType type = OK, bool modal = true)`
**Description:** Creates a dialog associated with the `parent` window. `title` is the dialog title text (often shown at top of dialog), `message` is the body text content. `type` specifies which buttons are included (see `MessageBoxType` enum for combinations, e.g., OK, YesNo, etc.). `modal` determines if this is a modal dialog (true) or modeless (false). The dialog is initially not visible (you must call `show()` to display it).
- `MessageBoxType` enumeration defines common button sets:
- `OK` only an "OK" button.
- `OKCancel` "OK" and "Cancel" buttons.
- `YesNo` "Yes" and "No" buttons.
- `YesNoCancel` "Yes", "No", and "Cancel".
- `RetryCancel` "Retry" and "Cancel".
- `AbortRetryIgnore` "Abort", "Retry", "Ignore".
- Modal dialogs (`modal == true`): When `show()` is called, it will block the current thread until the dialog is closed (the internal `show()` implementation runs its own loop). This is useful for confirmations or critical inputs that need immediate resolution. After `show()` returns, you can get the result via `getResult()`.
- Modeless dialogs (`modal == false`): When `show()` is called, it returns immediately (the dialog remains open, but the program flow continues and the main event loop will handle the dialogs interactions concurrently). To get the result, you typically provide a callback or check `getResult()` after the dialog closes (e.g., via a callback or a flag).
- **Component Elements of Dialog:**
- **Title label:** A `Label` (stored in `title`) showing the dialog title at top.
- **Message text:** The main content text, which can be multi-line. It's internally split into `lines` and drawn in the dialog client area.
- **Buttons:** One to three `Button` controls at the bottom of the dialog, depending on `MessageBoxType`. They are usually centered or evenly spaced.
- **Close (X) button:** A small `Button` in the top-right corner to close the dialog (especially for modeless dialogs, acts like a Cancel).
- **Background style:** Typically a rounded rectangle background with a slight border. There is no actual transparency or blur (the background might just capture what's behind for restoration when closed).
- **Key Properties:**
- `bool modal;` Whether the dialog is modal.
- `titleText` The dialog's title text string.
- `message` The dialog's message content string.
- `MessageBoxType type;` The type of dialog (determines buttons).
- `std::vector<std::string> lines;` The message content split into lines (populated internally).
- `std::unique_ptr<Label> title;` Label control for title.
- `Button* closeButton;` Pointer to the "X" close button.
- `Window& hWnd;` Reference to the parent window (for sending refresh signals, etc.).
- `StellarX::MessageBoxResult result;` The result of the dialog (which button was clicked by the user). Common values: OK=1, Cancel=2, Yes=6, No=7, etc. (Matches typical message box results).
- `std::function<void(MessageBoxResult)> resultCallback;` If set (for modeless use), a callback function to be called when the dialog is closed with the final result.
- (Other internal flags track if initialization is needed, if a cleanup is pending, etc.)
- **Main Methods:**
- `void show();`
**Description:** Displays the dialog.
- If `modal == true`, this function enters a loop that blocks the rest of the application until the dialog is closed. During this period, it intercepts events in its own loop (preventing main window events).
- If `modal == false`, this function simply makes the dialog visible and returns immediately; the dialog will be handled by the main window's event loop.
- Note: The implementation sets `dirty` and possibly runs its own flush for modals, and uses `shouldClose` to break out of the modal loop if needed.
- `void closeDialog();`
**Description:** Closes the dialog.
- It hides the dialog (`show = false`), marks it for cleanup, and signals the parent window to refresh other controls (by marking them dirty).
- If a `resultCallback` is set and the dialog is modeless, it invokes the callback with the result.
- This is typically called internally when a button is clicked (like Cancel or the X button).
- `StellarX::MessageBoxResult getResult() const;`
**Description:** Returns the `MessageBoxResult` code indicating which button the user pressed. For modal dialogs, you call this after `show()` returns to get the user's choice. For modeless, you'll typically get the result from the callback rather than polling this function.
- `void setTitle(const std::string& title);` / `void setMessage(const std::string& message);`
**Description:** Changes the dialog's title or message text. Changing the message triggers re-splitting into lines and recalculating sizes (the code marks dirty and reinitializes layout).
- `void setType(StellarX::MessageBoxType newType);`
**Description:** Changes the dialog's button configuration. This will recreate the buttons layout (calls `initButtons()` internally) and mark the dialog dirty.
- `void setModal(bool modal);`
**Description:** Sets the modal property (should be done before showing). Typically you decide at creation whether a dialog is modal or not; toggling at runtime is rare.
- `void setResult(StellarX::MessageBoxResult res);`
**Description:** Sets the dialog's result code. This is called internally when a button is clicked (for example, clicking "Yes" calls `setResult(MessageBoxResult::Yes)`).
- `void setInitialization(bool init);`
**Description:** If `init` is true, it performs an initialization of dialog size and captures the background. This method is used before showing the dialog (particularly by `MessageBox::showModal` and `showAsync`) to prepare the dialog geometry off-screen. Essentially, it calls `initDialogSize()` and saves the background behind where the dialog will appear (so that it can restore it on close).
- `void setResultCallback(std::function<void(MessageBoxResult)> cb);`
**Description:** Sets a callback function to be called with the dialog result when a modeless dialog is closed. Use this for asynchronous dialog handling.
- *Private internal methods (for implementation):*
- `initButtons()` Creates the appropriate `Button` objects for the dialog's `type`, positions them, and sets their onClick listeners to call `setResult` and `closeDialog`. For example, in a YesNo dialog, it creates "Yes" and "No" buttons and sets their listeners to `SetResult(Yes)` / `...No` and `closeDialog()` accordingly.
- `initCloseButton()` Creates the "X" close button at top-right of dialog (small size). Its onClick calls `SetResult(Cancel)` (or similarly appropriate default) and triggers `hWnd.dialogClose = true` and `closeDialog()`.
- `initTitle()` Creates the title Label at the top inside the dialog.
- `splitMessageLines()` Splits the `message` string by newline characters into the `lines` vector for drawing.
- `getTextSize()` Calculates the maximum text width and single line height among the `lines`. It does this by setting the text style and measuring each line (`textwidth` & `textheight` calls). It stores results in `textWidth` and `textHeight`.
- `initDialogSize()` Computes the dialog's required width and height based on:
- The greater of text area width (textWidth + margins) and button area width (depending on number of buttons).
- For height: sums title bar height, spacing to text, total text block height (line count * textHeight + line spacing), and button area height.
- Enforces a minimum width (in code, 200 px).
- Centers the dialog on the parent window by setting `x,y` such that the dialog is at window center.
- Also sets a default text style height (e.g., 20 px).
- Then calls `initButtons()`, `initTitle()`, and `initCloseButton()` to create all child controls.
- `performDelayedCleanup()` Handles the delayed destruction of the dialog's content after its closed. It:
- Marks `isCleaning = true` to avoid reentry.
- Clears all child controls (`controls.clear()`).
- Resets pointers (closeButton, title).
- If a background image was saved and still present (`hasSnap` is true), it restores the screen background (`restBackground()`) and discards the snapshot (`discardBackground()`) to remove any ghost image of dialog.
- Resets flags (needsInitialization, pendingCleanup, isCleaning, shouldClose).
- This is called by the event loop after the dialog is hidden, to safely remove the dialog from UI and restore background.
- `void draw() override;`
**Description:** Draws the dialog. It combines container drawing with additional text drawing:
- If dialog is not visible (`show == false`), it does nothing (except if `pendingCleanup` and not cleaning, it calls `performDelayedCleanup()`, but that's in handleEvent rather than draw).
- If `needsInitialization` is true and dialog is now visible, it calls `initDialogSize()` to compute layout and then sets `needsInitialization = false`. This ensures the dialog's size and children are all properly set up on first draw.
- It then checks `dirty` and `show`, and if both are true:
- Saves drawing style.
- If it's the first draw and `saveBkImage` is null, it calls `saveBackground(...)` to capture what's behind the dialog.
- Sets the border color, border width, background color, and shape on the base Canvas part (calls `Canvas::setBorderColor`, etc.) to round rectangle and appropriate style.
- Marks all child controls dirty (so they will be redrawn).
- Calls `Canvas::draw()` to draw the rounded rectangle background and all the child controls (like buttons, title).
- Then sets text color and style (for the message text) and calculates a starting y coordinate `ty` just below the title area (using `closeButtonHeight` and a margin).
- For each line in `lines`, it computes an x coordinate `tx` to center that line in the dialog (x + (width - textwidth(line))/2), and then outputs the text with `outtextxy(tx, ty, line)`.
- Moves `ty` down by textheight + 5 for the next line (5 px line spacing).
- Restores style, sets `dirty = false`.
- The base Canvas::draw already drew the background and the title label and buttons. So these text drawing steps are to overlay the message text content (which is not a separate control, but drawn directly).
- `bool handleEvent(const ExMessage& msg) override;`
**Description:** Handles events for the dialog:
- If the dialog is not visible (`!show`):
- If there's a `pendingCleanup` and not already cleaning, it calls `performDelayedCleanup()`.
- Returns false (no event consumed).
- If a cleanup is pending or in progress (`pendingCleanup || isCleaning`), it immediately returns false (dialog isn't interactive at this moment).
- If modal:
- If the user clicked outside the dialog (LButtonUp at a point outside `[x, y, x+width, y+height]`):
- It prints a bell (`std::cout << "\a"`) to alert (like system beep).
- Returns true (consumes the event) to prevent underlying controls from receiving the click.
- Then it attempts to dispatch the event to dialog's child controls:
- `consume = Canvas::handleEvent(msg)` calls the base container logic to let any button inside the dialog handle the event (e.g., if user clicked "OK", the button's handleEvent will consume it).
- If a child consumed it, `consume` becomes true.
- After dispatching, if `pendingCleanup && !isCleaning`, it calls `performDelayedCleanup()` (maybe if a button triggered immediate closure).
- Returns `consume` (so if a dialog button or modal logic handled the event, it returns true, otherwise false).
- The effect is that normal controls behind won't receive events while a modal dialog is open (due to the modal check swallowing outside clicks and because the Window event loop prioritizes dialogs).
- For modeless dialogs, events outside are not swallowed by Dialog (so user can interact with background controls as well, which is typical for modeless).
- **Usage Examples:**
It's more common to use `MessageBox` static methods to show dialogs (documented next), but you can use `Dialog` directly:
- **Modal example:**
```
Dialog confirm(window, "Confirm Delete", "Are you sure you want to delete?", StellarX::MessageBoxType::YesNo, true);
confirm.show();
if (confirm.getResult() == StellarX::MessageBoxResult::Yes) {
// proceed with delete
}
```
- **Modeless example:**
```
Dialog *notify = new Dialog(window, "Notice", "Background task started.", StellarX::MessageBoxType::OK, false);
notify->setResultCallback([](StellarX::MessageBoxResult res) {
// maybe log that user acknowledged
});
notify->show();
```
Here we allocate on heap (since for modeless we need it to persist beyond function scope), set a callback to handle result asynchronously, and show it. The main loop will handle its events. The callback will be invoked on close (OK click).
- Typically, prefer using `MessageBox::showModal` and `showAsync` which handle these patterns for you, including cleanup of the `Dialog` object when done.
### MessageBox Utility Class
**Description:** `StellarX::MessageBox` provides convenient static methods to display standard dialogs (message boxes) without manually managing `Dialog` objects. It mimics the idea of a system message box where you specify a message and get a user response.
- **Static Functions:**
- `static MessageBoxResult showModal(Window& wnd, const std::string& text, const std::string& caption = "提示", MessageBoxType type = MessageBoxType::OK);`
**Description:** Displays a modal message box on window `wnd` with given content. `caption` is the title of the dialog (e.g., "Error", "Confirmation"), `text` is the message to display, and `type` is the combination of buttons to include. This function blocks until the user closes the dialog. It returns a `MessageBoxResult` indicating which button the user clicked (e.g., `MessageBoxResult::OK` or `Cancel`, etc.). The caller can then branch logic based on this result.
- `static void showAsync(Window& wnd, const std::string& text, const std::string& caption = "提示", MessageBoxType type = MessageBoxType::OK, std::function<void(MessageBoxResult)> onResult = nullptr);`
**Description:** Displays a modeless (asynchronous) message box. It returns immediately, leaving the dialog on screen. If `onResult` callback is provided, it will be called once the user closes the dialog, with the `MessageBoxResult` of what they clicked. If a dialog with the same caption **and text** is already open on `wnd`, this function will emit a beep (`\a`) and not open another (to prevent duplicates).
- This duplicate check uses `wnd.hasNonModalDialogWithCaption(caption, text)` internally.
- If no callback is provided, the function simply opens the dialog and returns; you'll have to find another way to know when it's closed (perhaps by checking the `Dialog` via some global or so, but ideally you provide a callback).
- **Implementation Details:**
- `showModal`:
- Creates a `Dialog` (with modal = true) on the stack.
- Calls `dlg.setInitialization(true)` to prepare its layout and capture background before showing (to minimize flicker).
- Calls `dlg.show()`, which will block until the dialog is closed.
- When `show()` returns, it retrieves the result with `dlg.getResult()` and returns that.
- All memory is cleaned up on function exit since `dlg` was on stack.
- `showAsync`:
- First checks for duplicates; if found, prints a bell and returns without doing anything.
- Otherwise, creates a `Dialog` on the heap (with modal = false) using a unique_ptr. It then:
- Calls `dlgPtr->setInitialization(true)` to layout and capture background.
- If an `onResult` callback is provided, calls `dlgPtr->setResultCallback(std::move(onResult))` to register it.
- Uses `wnd.addDialog(std::move(dlg))` to add the dialog to the window's management list (transferring ownership to the window).
- Calls `dlgPtr->show()` to display the dialog modelessly.
- Then returns immediately. The dialog will operate asynchronously; when closed, it triggers `resultCallback` if set.
- **Typical Uses:**
- To display an information message and wait for user acknowledgment:
```
StellarX::MessageBox::showModal(mainWindow, "Operation completed successfully.", "Info", MessageBoxType::OK);
```
This will show an OK dialog and block until user presses OK.
- To ask a yes/no question:
```
auto res = StellarX::MessageBox::showModal(mainWindow, "Delete this file?", "Confirm", MessageBoxType::YesNo);
if (res == MessageBoxResult::Yes) {
// delete the file
}
```
- To show a non-blocking notification:
```
StellarX::MessageBox::showAsync(mainWindow, "The update is downloading in the background.", "Update", MessageBoxType::OK);
// user can continue using app; the dialog will close when they press OK.
```
- To ask something in the background and get result via callback:
```
StellarX::MessageBox::showAsync(mainWindow, "New version available. Download now?", "Update Available", MessageBoxType::YesNo,
[](StellarX::MessageBoxResult res) {
if (res == StellarX::MessageBoxResult::Yes) {
startDownload();
}
});
```
Here, the lambda will execute once the user clicks Yes or No (Yes triggers `startDownload()` in this example).
Using these static methods is recommended because they handle the creation and cleanup of `Dialog` internally, and provide a simple synchronous interface for modal dialogs and an event-driven interface for modeless dialogs.
## Table Class
### Class Overview and Purpose
The **Table** class is an advanced table UI control that supports pagination and displaying large datasets[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/include/StellarX/table.h#L2-L10). It provides a comprehensive data grid functionality, including column headers, data rows, and pagination navigation, similar to a spreadsheet. The Table control automatically computes appropriate column widths and row heights, and it allows customization of border style, fill mode, and text font style to fit various UI design requirements. With its built-in paging mechanism, Table can efficiently handle and present large amounts of data without overwhelming the interface, utilizing background buffering to optimize rendering and prevent flicker during redraws.
**Key Features:**
- **Pagination Support:** Automatically handles pagination by computing total pages based on data size and configurable rows-per-page, and provides **Previous/Next** navigation buttons with a page indicator[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/README.md#L274-L282). This enables users to browse through data that spans multiple pages easily.
- **Customization:** Developers can configure the number of rows per page, toggle the visibility of page navigation buttons, and adjust visual aspects such as border color, background color, fill mode (solid fill or transparent), and line style to match the application's theme.
- **Efficient Rendering:** The Table control uses double-buffering techniques by capturing the background under the table and only redrawing changed areas[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L210-L219)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L241-L249). This minimizes redraw artifacts and improves performance when updating the table content or appearance.
- **Typical Use Cases:** Ideal for displaying tabular data like lists, reports, and records in a GUI application[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/include/StellarX/table.h#L14-L17). For example, it can be used to show database query results, log entries, or statistical reports in a scrollable, paginated view, allowing users to easily read and navigate the information.
### Public Member Functions
#### Table(int x, int y)
**Prototype:** `Table(int x, int y)`
**Parameters:**
- `x` The X coordinate for the tables top-left corner position.
- `y` The Y coordinate for the tables top-left corner position.
**Return Value:** None (constructor).
**Description:** Constructs a new Table control at the specified position on the screen[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L164-L171). Initially, the table has no headers or data; its width and height are set to 0 and will be calculated once data is provided and headers are set. The `x` and `y` coordinates position the table within the parent window or container. Internally, Table inherits from the base `Control` class, so it is created with an initial area that will expand based on content.
**Usage Scenario:** Use this constructor when you need to create a table in your applications UI. For example, after creating a main window, you might call `auto table = std::make_unique<Table>(50, 50);` to place a Table at coordinates (50,50). You would then set up its headers and data before rendering it. The Table can be added to a Window or Canvas as a sub-control for display.
#### ~Table()
**Prototype:** `~Table()`
**Parameters:** None.
**Return Value:** None.
**Description:** Destructor for the Table class. It is called automatically when a Table object is destroyed, and it ensures that all internally allocated resources are freed. This includes deleting any internal controls such as the pagination buttons, page number label, and the background image buffer that were created during the Tables operation[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L169-L178). By cleaning up these resources, the destructor prevents memory leaks.
**Usage Scenario:** Generally, you do not need to call this explicitly; it will be invoked when the Table goes out of scope or the program terminates. Understanding the destructors behavior is useful for developers to ensure that if a Table is dynamically allocated (e.g., via `new` or `make_unique`), it should be deleted or allowed to go out of scope to trigger cleanup of internal components.
#### draw()
**Prototype:** `void draw() override`
**Parameters:** None.
**Return Value:** None.
**Description:** Overrides the `Control::draw()` method to render the table onto the screen[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L185-L194)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L252-L260). When `draw()` is called, the Table will draw its column headers, the data rows for the current page, the page number label, and the navigation buttons (if they are enabled). The drawing routine uses the current text style, border color, background color, fill mode, and line style settings that have been configured. The Table employs a “dirty flag” mechanism to optimize rendering: it will only redraw its content if something has changed (such as data or style) that marks it as needing an update[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L187-L196)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L258-L266). If `dirty` is false, calling `draw()` has no effect. Internally, the function also takes care of restoring any saved background and drawing with minimal flicker.
**Usage Scenario:** In most cases, you do not need to call `table.draw()` manually, as the frameworks window or event loop will handle drawing. Typically, after adding the Table to a Window and calling the windows `draw()`, the Table will be drawn. However, if you update the Tables content or appearance at runtime (for example, in response to an event) and want to refresh immediately, you may call `table.draw()` to force a redraw. This will ensure the tables display is updated with any new data or style changes.
#### handleEvent(const ExMessage& msg)
**Prototype:** `bool handleEvent(const ExMessage& msg) override`
**Parameters:**
- `msg` An event message (`ExMessage` from EasyX library) containing details of a user input event (mouse click, key press, etc.).
**Return Value:** Boolean, indicating whether the event was handled by the Table. Returns `true` if the event was consumed (handled) by this Table control, or `false` if the Table did not handle the event.
**Description:** Overrides the `Control::handleEvent()` method to process user input events relevant to the Table. For the Table control, `handleEvent` primarily deals with events on the pagination buttons. If page navigation buttons are enabled (`isShowPageButton` is true), this function will forward the incoming event to the internal "Previous Page" and "Next Page" Button controls for handling[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L274-L283). For example, if the user clicks one of these buttons, the respective internal Button will capture the event, update the Tables `currentPage` (decrement or increment it), and trigger a redraw of the table to show the new page. In such a case, `handleEvent` returns `true` to indicate the event was handled (and no further processing is needed). If the page buttons are hidden (`isShowPageButton == false`), the Table will ignore all events and always return `false`[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L276-L283), as there is no interactive element for the user to manipulate directly in the Table.
**Usage Scenario:** In normal usage, you do not call this method directly. The frameworks main loop will call `handleEvent` on each control (including Table) to propagate user inputs. However, understanding this function is useful if you plan to extend the Table or integrate it into a custom event handling flow. For instance, when integrating Table into a Window, the Windows `runEventLoop()` will pass events to `table.handleEvent(msg)` automatically. If you have disabled the default page buttons, you might implement a custom paging control elsewhere; in that case, Tables `handleEvent` would not consume events, and you could handle them in your custom controls.
#### setHeaders(std::initializer_liststd::string headers)
**Prototype:** `void setHeaders(std::initializer_list<std::string> headers)`
**Parameters:**
- `headers` An initializer list of strings, each representing a column header title.
**Return Value:** None.
**Description:** Defines the column headers of the table. This function clears any existing headers and sets up new headers as provided by the list[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L288-L296). Each string in the list becomes the title of a column in the table header. After calling this, the Table marks itself as needing to recalculate cell dimensions and to redraw the headers[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L291-L295) on the next draw. The number of headers determines the number of columns in the table; any subsequent data rows added should match this column count.
**Usage Scenario:** Call this after creating a Table and before adding data. For example: `table.setHeaders({"Name", "Age", "Occupation"});` will configure the table to have three columns titled "Name", "Age", and "Occupation". Defining headers is typically the first step in preparing the Table for use. Once headers are set, you can proceed to populate data rows with `setData`. Changing headers after data has been added is possible but not recommended, as the existing data might not align with the new columns (if the counts differ); if you must do so, you should reset or adjust the data accordingly.
#### setData(std::vectorstd::string data) / setData(std::initializer_list<std::vectorstd::string> data)
**Prototype 1:** `void setData(std::vector<std::string> data)`
**Parameters:**
- `data` A vector of strings representing a single row of data, where each element corresponds to a cell under the respective column.
**Return Value:** None.
**Description:** Adds one row of data to the table. If the number of elements in the provided vector is less than the number of headers (columns), the function will append empty strings to the vector until it matches the header count[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L300-L308). This ensures that every row has a value (or placeholder) for each column. The row is then appended to the Tables internal data storage, and the total page count (`totalPages`) is recalculated based on the new total number of rows and the current `rowsPerPage` setting[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L302-L308). The calculation rounds up to ensure at least one page[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L303-L308). After adding the row, the Table is marked as needing a layout recompute and redraw.
**Usage Scenario:** Use this function to build the table row by row. For example, in a loop reading records from a file or database, you might call `table.setData({val1, val2, val3});` for each record to append it as a new row. This is especially useful for dynamically updating the table with new entries (e.g., real-time log updates). Each call adds the row at the end of the data and the Tables display will include it on the appropriate page (immediately if it falls on the current page and you redraw, or after navigation).
**Prototype 2:** `void setData(std::initializer_list<std::vector<std::string>> data)`
**Parameters:**
- `data` An initializer list of rows, where each row is represented by a `std::vector<std::string>` (as described above).
**Return Value:** None.
**Description:** Adds multiple rows of data to the table in one call. For each row provided in the initializer list, the function checks if its number of elements matches the number of table headers; if a row is shorter, empty strings will be appended until its length equals the header count[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L313-L321). Each row (adjusted as needed) is then appended to the tables data. After inserting all the rows, the total page count is recalculated accordingly, ensuring it is at least 1[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L322-L328). Like the single-row version, this overload marks the table as needing re-layout and redraw. Note that this function does **not** clear existing data; new rows are appended to any data already present.
**Usage Scenario:** This is convenient for initializing the table with a batch of data. For example, if you already have a collection of rows ready (perhaps from a data structure or file), you can call `table.setData({row1, row2, row3});` to add them all at once instead of calling the single-row version repeatedly. It simplifies populating the table with initial data. If you intend to replace all existing data, you may want to clear the old data (by resetting the Table or using the returned data vector) before calling this overload. Use this when you have a static set of data to display, especially during initialization or when loading a new dataset into the table.
#### setRowsPerPage(int rows)
**Prototype:** `void setRowsPerPage(int rows)`
**Parameters:**
- `rows` The number of data rows to display per page.
**Return Value:** None.
**Description:** Sets the pagination size of the table by specifying how many rows are shown on each page. This function updates the internal `rowsPerPage` setting and recalculates the total number of pages (`totalPages`) based on the current total number of data rows[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L332-L338). If the recalculation results in less than 1 page, it defaults to 1 to maintain a valid state. Changing this value will affect the layout (particularly the vertical size of the table and the distribution of data across pages), so the Table marks itself for resizing of cells and redraw. The current page (`currentPage`) is not explicitly adjusted in this function, so if the new `rowsPerPage` significantly reduces the total pages, the table may internally handle out-of-range page indices on the next draw by capping the current page to the last page.
**Usage Scenario:** Use this to adjust how dense the table appears in terms of data per page. For instance, if a table initially shows 5 rows per page by default but you want to show more data on a larger screen, calling `table.setRowsPerPage(10);` will double the number of rows shown at once, reducing the total page count roughly by half. This is useful for user preferences (show more or fewer items per page) or responsive design considerations. It can be called before or after adding data. If called after data is loaded and the current page is beyond the new total pages, the next redraw will naturally show the last page of data.
#### showPageButton(bool isShow)
**Prototype:** `void showPageButton(bool isShow)`
**Parameters:**
- `isShow` A boolean flag indicating whether the page navigation buttons (Previous/Next) should be displayed. `true` to show the buttons; `false` to hide them.
**Return Value:** None.
**Description:** Toggles the visibility of the pagination control buttons of the table. When set to `false`, the “Previous Page” and “Next Page” buttons will not be drawn, and the user will not have a UI control to navigate between pages (the table will effectively remain on whatever the current page is, unless changed programmatically). When set to `true`, the buttons are visible, allowing the user to click them to change pages. Hiding the buttons does not alter the data or current page internally; it purely affects the UI and event handling (when hidden, `handleEvent` will ignore navigation clicks as described above). Any call to this function will mark the table as dirty so that on the next draw the buttons will be shown/hidden accordingly.
**Usage Scenario:** Useful when the dataset fits on one page or when you want to control pagination through a different means. For example, if your table only has a few rows that all fit on a single page, you might call `table.showPageButton(false);` to simplify the interface by removing unnecessary controls. Conversely, if you later load more data that requires paging, you can call `showPageButton(true)` to reveal the navigation. It can also be toggled in response to user actions (like a "show all data" toggle that disables manual paging).
#### setTableBorder(COLORREF color)
**Prototype:** `void setTableBorder(COLORREF color)`
**Parameters:**
- `color` A COLORREF value specifying the new color for the tables border lines (grid lines). This is typically created by the `RGB(r,g,b)` macro on Windows (e.g., `RGB(0,0,0)` for black).
**Return Value:** None.
**Description:** Sets the color used to draw the tables cell borders and outline. After calling this, the Table updates its internal border color and marks itself for redraw. The next time the table is drawn, all grid lines and borders will be rendered in the specified color[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L192-L199). By default, the tables border color is black. Changing the border color can be useful to match the overall UI theme or to highlight the table. This affects the lines around each cell and the table perimeter.
**Usage Scenario:** Use this to customize the appearance of the tables grid. For example, in a dark-themed application you might use a lighter border color for contrast, or in a light theme, a subtle gray to avoid a heavy look: `table.setTableBorder(RGB(200, 200, 200));`. This is purely a cosmetic setting and can be changed anytime (even at runtime) to alter the tables look. Remember to refresh the display (via `draw()` or window redraw) to see the effect immediately.
#### setTableBk(COLORREF color)
**Prototype:** `void setTableBk(COLORREF color)`
**Parameters:**
- `color` A COLORREF specifying the background fill color for the tables cells.
**Return Value:** None.
**Description:** Sets the background color used to fill each cell of the table. This updates the internal background color property and marks the Table as needing redraw. During the next `draw()`, each cells background will be filled with the given color (provided the fill mode is solid). By default, table cell backgrounds are white. Changing this can provide alternating row colors or highlight the table area against the window background. If the fill mode is set to Null (transparent), this color will not be visible since cells wont be filled with a solid color.
**Usage Scenario:** To change the background color of the table for styling or readability. For instance, `table.setTableBk(RGB(240, 240, 240));` would set a light gray background for all cells, which can reduce glare compared to bright white. This can be paired with custom text colors for better contrast. If you want to implement alternating row colors (like “zebra striping”), the current Table API does not support per-row color via this function directly you would need to draw custom backgrounds or modify the data drawing logic.
#### setTableFillMode(StellarX::FillMode mode)
**Prototype:** `void setTableFillMode(StellarX::FillMode mode)`
**Parameters:**
- `mode` A `StellarX::FillMode` value indicating the fill pattern mode for the cell backgrounds. Common values are `FillMode::Solid` (solid color fill) or `FillMode::Null` (no fill/transparent background). Other modes like `Hatched`, `Pattern`, etc., are also defined but may fall back to Solid if not supported.
**Return Value:** None.
**Description:** Configures how the tables cell background is filled when drawn. In practice, this toggles between solid fill and no fill for the Table (the current implementation treats any mode other than Solid or Null as Solid)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L360-L369). Setting `FillMode::Solid` means cells will be drawn with the current background color (`tableBk`), whereas `FillMode::Null` means cell backgrounds will not be drawn (making them transparent, showing whatever was behind the table). This function also propagates the fill mode and text style to the internal navigation buttons and page number label to ensure visual consistency[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L365-L373). For example, if you choose Null mode (transparent), the page number labels background will be made transparent as well. Any change here marks the Table (and its sub-controls) for redraw.
**Usage Scenario:** Use this when you need to adjust whether the tables background is visible. A typical use is to set `table.setTableFillMode(StellarX::FillMode::Null);` if you want the table to overlay on a custom background (so only text and borders render, letting the underlying image or color show through). Otherwise, keep it as Solid to use a uniform background color for cells. The default is Solid fill. Be aware that patterns or hatched fills are not fully supported in this version of the framework; even if set, they will revert to a solid fill for simplicity[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L360-L368).
#### setTableLineStyle(StellarX::LineStyle style)
**Prototype:** `void setTableLineStyle(StellarX::LineStyle style)`
**Parameters:**
- `style` A `StellarX::LineStyle` value indicating the style of the grid lines (e.g., `LineStyle::Solid` for solid lines, `LineStyle::Dash` for dashed lines, `LineStyle::Dot` for dotted lines, etc.).
**Return Value:** None.
**Description:** Sets the style used for drawing the tables border and grid lines. Changing this will cause the table to use the specified line style (if supported by the underlying graphics library) for all cell borders and the outline on the next redraw. Examples include dashed or dotted lines for a lighter look compared to solid lines. After calling this, the line style property is updated and the table is marked dirty for redrawing. Note that the actual rendering of different line styles depends on the graphics library capabilities.
**Usage Scenario:** This is a visual customization for the tables grid. For instance, calling `table.setTableLineStyle(StellarX::LineStyle::Dash);` will make the table draw its cell borders with dashed lines, which might be desirable in a print preview or a specialized UI theme. Use it to differentiate sections or to achieve a stylistic effect. Combine it with appropriate border color and width for best results. If a particular line style is not obvious on screen, ensure that line width is 1 and the color contrasts with the background.
#### setTableBorderWidth(int width)
**Prototype:** `void setTableBorderWidth(int width)`
**Parameters:**
- `width` The thickness of the border lines in pixels. Must be a positive integer.
**Return Value:** None.
**Description:** Sets the width (thickness) of the lines used for the tables cell borders and outline. By default, grid lines are 1 pixel thick. Increasing this value will make the tables grid lines thicker/bolder, while a value of 1 keeps them thin. When this property is changed, the table marks itself for redraw and will use the new line width for drawing borders on the next `draw()`. If a thicker border is set (greater than 1), the Tables drawing logic takes into account the larger stroke when restoring the background to avoid artifacts (adjusting the area it refreshes)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L220-L228)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L240-L248).
**Usage Scenario:** Use a larger border width when you want the table grid to stand out more, such as in a presentation or when displaying on a high-DPI screen where a 1-pixel line may appear too thin. For example, `table.setTableBorderWidth(2);` will draw grid lines at 2 pixels thickness, making the table look more pronounced. Be cautious with very thick lines as they can clutter the appearance and might overlap cell content if too large relative to cell size. Always test the appearance after changing this setting.
#### Accessor Methods (Getters)
The following methods allow you to query the Tables current state or configuration:
- **int getCurrentPage() const** Returns the current page index (1-based) that the table is displaying[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L390-L398). For example, if the table is showing the first page of data, this returns 1; if the user has navigated to the second page, it returns 2, and so on. This can be used to display or log the current page number.
- **int getTotalPages() const** Returns the total number of pages of data in the table given the current dataset and rows-per-page setting[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L394-L402). This is calculated as ceil(total_rows / rowsPerPage). Its useful for informing the user (e.g., “Page 2 of 5”) or for logic that might depend on whether more pages are available.
- **int getRowsPerPage() const** Returns the number of data rows the table is set to display on each page[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L400-L408). This reflects the value last set by `setRowsPerPage`. It can be used to verify the current pagination setting or to adjust external controls accordingly.
- **bool getShowPageButton() const** Returns the current setting of whether the pagination buttons are visible[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L404-L412) (`true` if they are shown, `false` if hidden). This could be checked to decide if manual navigation UI should be enabled or if an alternate navigation method is needed.
- **COLORREF getTableBorder() const** Returns the current color (COLORREF) used for the tables border/grid lines[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L410-L418). For instance, `RGB(0,0,0)` for black. This allows you to retrieve the color if you need to apply the same color elsewhere or for saving the configuration.
- **COLORREF getTableBk() const** Returns the current background fill color (COLORREF) of the tables cells[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L414-L422).
- **StellarX::FillMode getTableFillMode() const** Returns the current fill mode for the tables cells[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L420-L428), as a `StellarX::FillMode` enum (e.g., Solid or Null). This lets you check whether the table is drawing solid backgrounds or is in transparent mode.
- **StellarX::LineStyle getTableLineStyle() const** Returns the current line style used for the tables grid lines[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L424-L432) (as a `StellarX::LineStyle` enum). You can use this to inspect the style (solid, dashed, etc.) programmatically.
- **std::vectorstd::string getHeaders() const** Returns a copy of the current list of header strings[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L430-L438). The returned vector contains each columns header text in order. This can be used to retrieve the headers for display elsewhere or for serialization.
- **std::vector<std::vectorstd::string> getData() const** Returns a copy of all the tables data[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L434-L442). The returned value is a 2D vector of strings, where each inner vector represents one row of the table (with elements corresponding to each columns value in that row). This is useful for exporting the table data or performing computations on the data outside of the table (like searching or sorting, which would typically be done on the data and then reapplied to the table).
- **int getTableBorderWidth() const** Returns the current width (in pixels) of the tables border lines[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L440-L446).
All these getter functions are `const` and do not modify the Tables state. They are intended for retrieving the tables configuration and content at any moment. For example, after populating the table, you might call `getTotalPages()` to update a UI element that shows the total page count, or use `getData()` to verify the data loaded into the table.
### Public Member Variables
- **StellarX::ControlText textStyle** A struct that defines the font and style used for text rendered in the table (including header text, cell content, and the page number label). By modifying this public member, you can customize the appearance of the tables text. For instance, you can set `textStyle.nHeight` to change the font height (size), `textStyle.lpszFace` to change the font family name, `textStyle.color` to change the text color, and other attributes such as `nWeight` for boldness, `bItalic` for italics, `bUnderline` for underline, etc.[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/include/StellarX/CoreTypes.h#L134-L142). The Table uses these settings when drawing text via EasyX functions. The default `ControlText` values are initialized to a standard font (e.g., a default Chinese font "微软雅黑" as in the code, or "SimSun") at a default size and black color. Developers can adjust `textStyle` any time before calling `draw()` to apply new text styles. Notably, the Tables internal prev/next Buttons and page number Label also use the Tables `textStyle` to ensure consistency in typography across the entire control. For example, to make the table text larger and blue:
```
table.textStyle.nHeight = 20; // set font height
table.textStyle.color = RGB(0, 0, 128); // set text color to navy blue
```
After adjusting, calling `table.draw()` (or a window redraw) will render the table with the new font settings.
### Example
Below is an example demonstrating how to create and use a Table control in a StellarX application[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/README.en.md#L275-L283)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/README.en.md#L284-L290):
```c++
// Create a table control at position (50, 50)
auto myTable = std::make_unique<Table>(50, 50);
// Set column headers
myTable->setHeaders({ "ID", "Name", "Age", "Occupation" });
// Add data rows
myTable->setData({ "1", "Zhang San", "25", "Engineer" });
myTable->setData({ "2", "Li Si", "30", "Designer" });
myTable->setData({ "3", "Wang Wu", "28", "Product Manager" });
// Set to display 2 rows per page
myTable->setRowsPerPage(2);
// Customize text style and table appearance
myTable->textStyle.nHeight = 16; // set font height
myTable->setTableBorder(RGB(50, 50, 50)); // set border color (dark gray)
myTable->setTableBk(RGB(240, 240, 240)); // set background color (light gray)
// Add the table to the main window (assume mainWindow is a Window instance)
mainWindow.addControl(std::move(myTable));
```
In this code, we create a `Table` at coordinates (50, 50) on the window. We then define four column headers: "ID", "Name", "Age", "Occupation". Next, three rows of sample data are added to the table. The `setRowsPerPage(2)` call configures the table to show only 2 rows per page; since we added 3 rows of data, the table will have 2 pages (the first page showing the first 2 rows, and the second page showing the remaining 1 row). We then adjust the tables appearance by setting the font size (making text slightly larger), changing the border color to a dark gray, and the background color of cells to a light gray for better contrast. Finally, we add the table to a `mainWindow` (which is an instance of `Window`) so that it becomes part of the GUI. Once added, the windows drawing routine will render the table, and the event loop will ensure the tables pagination buttons respond to user clicks. Typically, after adding controls to a window, you would call `mainWindow.draw()` to render the initial UI, and then `mainWindow.runEventLoop()` to start processing events, allowing interactions like clicking the "Next" button to flip pages. This example illustrates the basic steps to integrate the Table control into an application and configure its data and appearance.
The above documentation covers all key classes in the StellarX framework. With these, developers can construct fully-featured GUI applications with windows, interactive buttons, text fields, and pop-up dialogs. Keep in mind StellarX is built on EasyX and thus inherits its platform limitation (Windows only) and certain modal behaviors (like `InputBox` use). It is ideal for educational purposes and lightweight tools requiring a simple GUI.

View File

@@ -1,298 +1,451 @@
# StellarX GUI Framework README # StellarX GUI Framework
[中文README](README.md) [中文文档](README.md)
------ ------
![GitHub all releases](https://img.shields.io/github/downloads/Ysm-04/StellarX/total) ![GitHub all releases](https://img.shields.io/github/downloads/Ysm-04/StellarX/total)
[![Star GitHub Repo](https://img.shields.io/github/stars/Ysm-04/StellarX.svg?style=social&label=Star%20This%20Repo)](https://github.com/Ysm-04/StellarX)
![Version](https://img.shields.io/badge/Version-2.2.0-brightgreen.svg) [![Star GitHub Repo](https://img.shields.io/github/stars/Ysm-04/StellarX.svg?style=social&label=Star%20This%20Repo)](https://github.com/Ysm-04/StellarX)
![Download](https://img.shields.io/badge/Download-2.2.0_Release-blue.svg)
![Version](https://img.shields.io/badge/Version-2.0.0-brightgreen.svg)
![Download](https://img.shields.io/badge/Download-2.0.0_Release-blue.svg)
![C++](https://img.shields.io/badge/C++-17+-00599C?logo=cplusplus&logoColor=white) ![C++](https://img.shields.io/badge/C++-17+-00599C?logo=cplusplus&logoColor=white)
![Windows](https://img.shields.io/badge/Platform-Windows-0078D6?logo=windows) ![Windows](https://img.shields.io/badge/Platform-Windows-0078D6?logo=windows)
![EasyX](https://img.shields.io/badge/Based_on-EasyX-00A0EA) ![EasyX](https://img.shields.io/badge/Based_on-EasyX-00A0EA)
![License](https://img.shields.io/badge/License-MIT-blue.svg) ![License](https://img.shields.io/badge/License-MIT-blue.svg)
![Architecture](https://img.shields.io/badge/Architecture-Modular%20OOP-brightgreen) ![Architecture](https://img.shields.io/badge/Architecture-Modular%20OOP-brightgreen)
![CMake](https://img.shields.io/badge/Build-CMake-064F8C?logo=cmake) ![CMake](https://img.shields.io/badge/Build-CMake-064F8C?logo=cmake)
> **Bounded by the stars, light as dust.”** — An ultra-lightweight, highly modular, native C++ GUI framework for Windows. > **"Bound by Stars, Light as Dust"** — A native C++ GUI framework for Windows platform, featuring extreme lightweight and high modularity.
`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. `StellarX` was born from resistance against "overly bloated" modern GUI frameworks. It rejects dependencies that often reach hundreds of MB, long compilation times, and steep learning curves, choosing to return to the essence: solving core desktop application development needs with the most concise code, clearest architecture, and highest efficiency.
This is a **teaching-grade and tooling-grade** framework that helps developers understand GUI fundamentals and quickly build lightweight utilities. It is a **pure teaching-level, tool-level framework** designed to help developers deeply understand GUI principles and quickly build lightweight Windows tools.
------ ---
## **🆕 v2.2.1 (Hotfix for v2.2.0)**
- Addressed a flickering issue that occurred when using the Canvas and TabControl containers.
- Fixed issues where border remnants and functional buttons could persist after closing a Dialog.
For details, please refer to the [CHANGELOG.en](CHANGELOG.en.md).
## Whats new in v2.2.0
- **New TabControl for multi-page tabbed UIs:** With `TabControl`, its easy to create a tabbed layout. Tabs can be arranged on the top, bottom, left, or right, and clicking switches the displayed page. Suitable for settings panels and multi-view switching.
- **Enhanced control show/hide and resize responsiveness:** All controls now share a unified interface (`setIsVisible`) to toggle visibility. When a container control is hidden, its child controls automatically hide/show with it. Meanwhile, we introduce `onWindowResize` for controls to respond to window size changes so elements update in sync after resizing, eliminating artifacts or misalignment.
- **Refined text-style mechanism:** The Label control now uses a unified `ControlText` style structure. Developers can easily customize font, color, size, etc. (replacing older interfaces, and more flexible). Button Tooltips also support richer customization and different texts for toggle states.
- **Other improvements:** Dialog management gains de-duplication to prevent identical prompts from popping up repeatedly. Several bug fixes and refresh optimizations further improve stability.
See `CHANGELOG.md / CHANGELOG.en.md` for the full list.
------
## 📦 Project Structure & Design Philosophy ## 📦 Project Structure & Design Philosophy
StellarX adopts classic **OOP** and **modular** design with a clear structure: StellarX framework adopts classic **Object-Oriented** and **modular** design with a clear and standardized project structure:
```markdown
```
StellarX/ StellarX/
├── include/ ├── include/ # Header files directory
│ └── StellarX/ │ └── StellarX/ # Framework header files
│ ├── StellarX.h │ ├── StellarX.h # Main include header - one-click import of entire framework
│ ├── CoreTypes.h # single source of truth (enums/structs) │ ├── CoreTypes.h # ★ Core ★ - Single source of truth for all enums and structs
│ ├── Control.h │ ├── Control.h # Abstract base class - defines unified interface for all controls
│ ├── Button.h │ ├── Button.h # Button control
│ ├── Window.h │ ├── Window.h # Window management
│ ├── Label.h │ ├── Label.h # Label control
│ ├── TextBox.h │ ├── TextBox.h # Text box control
│ ├── TabControl.h #v2.2.0 │ ├── Canvas.h # Canvas container
│ ├── Canvas.h │ ├── Dialog.h # Dialog control (new in v2.0.0)
│ ├── Dialog.h │ ├── MessageBox.h # Message box factory (new in v2.0.0)
── MessageBox.h ── Table.h # Table control
│ └── Table.h ├── src/ # Source files directory
├── 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
│ ├── TabControl.cpp #v2.2.0 │ ├── Table.cpp
│ ├── Table.cpp │ ├── Dialog.cpp # v2.0.0 new
── Dialog.cpp ── MessageBox.cpp # v2.0.0 new
│ └── MessageBox.cpp ├── examples/ # Example code directory
├── examples/ │ └── demo.cpp # Basic demonstration
│ └── demo.cpp ├── docs/ # Documentation directory
├── docs/ │ └── CODE_OF_CONDUCT.md # Code of Conduct
│ └── CODE_OF_CONDUCT.md ├── CMakeLists.txt # CMake build configuration
├── CMakeLists.txt ├── CONTRIBUTING.md # Contribution guide
├── CONTRIBUTING.md ├── CHANGELOG.md # Changelog
├── CHANGELOG.md ├── Doxyfile # Doxygen configuration
├── CHANGELOG.en.md ├── LICENSE # MIT License
── Doxyfile ── README.md # Project description
├── LICENSE
├──API 文档.md
├──API Documentation.en.md
└── README.md
``` ```
**Design Philosophy:** ### **Design Philosophy:**
1. **Single Responsibility (SRP):** each class/file does exactly one thing. 1. **Single Responsibility Principle (SRP)**: Each class/file is responsible for one thing only.
2. **Dependency Inversion (DIP):** high-level modules depend on abstractions (`Control`), not concrete controls. 2. **Dependency Inversion Principle (DIP)**: High-level modules (like `Window`) don't depend on low-level modules (like `Button`), both depend on abstractions (`Control`).
3. **Open/Closed (OCP):** extend by inheriting from `Control` without modifying existing code. 3. **Open/Closed Principle (OCP)**: New controls can be easily extended by inheriting the `Control` base class without modifying existing code.
4. **Consistency:** unified `draw()` / `handleEvent()` across all controls. 4. **Consistency**: All controls share unified `draw()` and `handleEvent()` interfaces.
## 🚀 Core Features ## 🚀 Core Features
- **Ultra-lightweight:** no heavyweight external dependencies besides EasyX. - **Extreme Lightweight**: Core library compiles to only ~12MB, with zero external dependencies. Generated applications are compact.
- **Clear modules:** `CoreTypes.h` unifies types and enums. - **Clear Modular Architecture**: Uses `CoreTypes.h` to uniformly manage all types, eliminating duplicate definitions and greatly improving maintainability.
- **Native performance:** EasyX + Win32 for efficient execution and low memory (often <10 MB). - **Native C++ Performance**: Built directly on EasyX and Win32 API, providing near-native execution efficiency with very low memory footprint (typically <10MB).
- **Complete control set:** Button, Label, TextBox, Canvas, Table, Dialog, MessageBox, **TabControl**. - **Complete Control System**: Button, Label, TextBox, Canvas, Table, Dialog, and MessageBox factory.
- **Highly customizable:** colors; shapes (rectangle/rounded/circle/ellipse); fills; fonts—switchable via enums. - **Highly Customizable**: From control colors, shapes (rectangle, rounded, circle, ellipse) to fill modes and font styles, all have detailed enum support for easy customization.
- **Simple, intuitive API:** OOP design with clear semantics—code as documentation. - **Simple & Intuitive API**: Uses classic object-oriented design, code as documentation, low learning curve.
- **Standard project layout:** split `include/src`, CMake-friendly, easy to integrate or use out of the box. - **Standard Project Structure**: Adopts standard include/src separation structure, supports CMake build, easy to integrate and use.
- **Enhanced Event System**: v2.0.0 introduces event consumption mechanism, all `handleEvent` methods return `bool` indicating whether event was consumed, supporting finer-grained event propagation control.
- **Dialog System**: New complete dialog support, including modal and non-modal dialogs, automatically handling background saving and restoration.
------ ## ⚡ Quick Start (5 Minutes to Get Started)
## ⚡ Quick Start (5 minutes) > **🎯 Latest Version Download**
> Download pre-compiled library files and header files from [GitHub Releases](https://github.com/Ysm-04/StellarX/releases/latest) for quick integration into your project.
> Get the prebuilt package from [Releases](https://github.com/Ysm-04/StellarX/releases/latest). ### Environment Requirements
### Requirements - **OS**: Windows 10 or higher
- **Compiler**: C++17 supported compiler (e.g., **Visual Studio 2019+**)
- **OS:** Windows 10+ - **Graphics Library**: [EasyX](https://easyx.cn/) (2022 version or higher, please select the version matching your compiler during installation)
- **Compiler:** C++17 (e.g., VS 2019+) - **Build Tool**: CMake 3.12+ (optional, recommended)
- **Graphics:** [EasyX](https://easyx.cn/) 2022+ (matching your compiler)
- **Build:** CMake 3.12+ (optional)
### Install EasyX ### Install EasyX
1. Download the latest EasyX 1. Visit [EasyX official website](https://easyx.cn/) to download the latest version
2. Install components matching your Visual Studio version 2. Run the installer, select the version matching your Visual Studio version
3. The framework links automatically—no extra config needed 3. After installation, no additional configuration is needed, StellarX framework will automatically link EasyX
### Build with CMake (recommended) ### Method 1: Using CMake Build (Recommended)
``` 1. **Clone the project**:
git clone https://github.com/Ysm-04/StellarX.git
cd StellarX
mkdir build && cd build
cmake ..
cmake --build .
./examples/Demo
```
### Manual Integration ```bash
git clone https://github.com/Ysm-04/StellarX.git
cd StellarX
```
- Copy `include` and `src` 2. **Generate build system**:
- Add header search path: `include/StellarX/`
- Add all `.cpp` files to your project
### First Resizable Window ```bash
mkdir build
cd build
cmake ..
```
``` 3. **Compile the project**:
```bash
cmake --build .
```
4. **Run the example**:
```bash
./examples/Demo
```
### Method 2: Manual Integration into Existing Project
1. **Copy the include and src directories** to your project
2. **Configure include paths** to ensure the compiler can find the `include/StellarX/` directory
3. **Add all .cpp files** to your project for compilation
### Create Your First StellarX Application
```cpp
// Just include this one header to use all features
#include "StellarX.h" #include "StellarX.h"
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) // Program entry point (use WinMain for better compatibility)
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{ {
// Resizing enabled by default; current size is the minimum size // 1. Create a 640x480 window with white background, titled "My App"
Window mainWindow(800, 600, 0, RGB(255,255,255), "My StellarX App"); Window mainWindow(640, 480, 0, RGB(255, 255, 255), "My First StellarX App");
// 2. Create a button (managed with smart pointer)
auto myButton = std::make_unique<Button>(
250, 200, 140, 40, // x, y, width, height
"Click Me", // button text
StellarX::ButtonMode::NORMAL,
StellarX::ControlShape::ROUND_RECTANGLE
);
// 3. Set click event for the button (using Lambda expression)
myButton->setOnClickListener([&mainWindow]() {
// Use message box factory to create modal dialog
auto result = StellarX::MessageBox::ShowModal(
mainWindow,
"Welcome to StellarX GUI\r\nAuthor: Ysm-04",
"Greeting",
StellarX::MessageBoxType::OKCancel
);
// Handle dialog result
if (result == StellarX::MessageBoxResult::OK) {
// User clicked OK button
}
});
// 4. (Optional) Set button style
myButton->textStyle.nHeight = 20;
myButton->textStyle.color = RGB(0, 0, 128); // Dark blue text
myButton->setButtonBorder(RGB(0, 128, 255)); // Blue border
// 5. Add button to window
mainWindow.addControl(std::move(myButton));
// 6. Draw the window
mainWindow.draw(); mainWindow.draw();
// Add your controls... // 7. Enter message loop, wait for user interaction
// mainWindow.addControl(std::move(btn));
mainWindow.runEventLoop(); mainWindow.runEventLoop();
return 0; 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. 1. **Compile and run!** You'll see a window with a blue rounded button, clicking it will pop up a message box.
------ ## 📚 Core Types Detailed Explanation (`CoreTypes.h`)
## 📚 Core Types (excerpt from `CoreTypes.h`) All visual and behavioral properties of the StellarX framework are controlled through the elegant enums and structs defined in `CoreTypes.h`.
### Enums ### Enum Types (Enums)
| Enum | Description | Common values | | Enum Type | Description | Common Values |
| ------------------ | ---------------- | ------------------------------------------------------------ | | :--------------------- | :---------------------- | :----------------------------------------------------------- |
| `ControlShape` | Geometric shape | `RECTANGLE`, `B_RECTANGLE`, `ROUND_RECTANGLE`, `CIRCLE`, `ELLIPSE` | | **`ControlShape`** | Control geometric shape | `RECTANGLE`, `B_RECTANGLE`, `ROUND_RECTANGLE`, `CIRCLE`, `ELLIPSE`, etc. |
| `ButtonMode` | Button behavior | `NORMAL`, `TOGGLE`, `DISABLED` | | **`ButtonMode`** | Button behavior mode | `NORMAL`, `TOGGLE`, `DISABLED` |
| `TextBoxMode` | TextBox mode | `INPUT_MODE`, `READONLY_MODE` | | **`TextBoxMode`** | Text box mode | `INPUT_MODE`, `READONLY_MODE` |
| `FillMode` | Fill mode | `SOLID`, `NULL`, `HATCHED` | | **`FillMode`** | Graphics fill mode | `SOLID`, `NULL`, `HATCHED`, etc. |
| `FillStyle` | Pattern style | `HORIZONTAL`, `CROSS` | | **`FillStyle`** | Pattern fill style | `HORIZONTAL`, `CROSS`, etc. |
| `LineStyle` | Line style | `SOLID`, `DASH`, `DOT` | | **`LineStyle`** | Border line style | `SOLID`, `DASH`, `DOT`, etc. |
| `MessageBoxType` | Message box type | `OK`, `OKCancel`, `YesNo`, ... | | **`MessageBoxType`** | Message box type | `OK`, `OKCancel`, `YesNo`, `YesNoCancel`, `RetryCancel`, `AbortRetryIgnore` |
| `MessageBoxResult` | Result | `OK`, `Cancel`, `Yes`, `No`, `Abort`, `Retry`, `Ignore` | | **`MessageBoxResult`** | Message box result | `OK`, `Cancel`, `Yes`, `No`, `Abort`, `Retry`, `Ignore` |
| `TabPlacement` | Tab position | `Top`, `Bottom`, `Left`, `Right` |
### Structs ### Structs (Structs)
| Struct | Description | | Struct | Description |
| -------------- | ---------------------------------------------------- | | :----------------- | :----------------------------------------------------------- |
| `ControlText` | Font/size/color/bold/italic/underline/strike-through | | **`ControlText`** | Encapsulates all text style attributes, including font, size, color, bold, italic, underline, strikethrough, etc. |
| `RouRectangle` | Corner ellipse size for rounded rectangles | | **`RouRectangle`** | Defines rounded rectangle corner ellipse dimensions, contains width and height properties. |
------ **Usage Example:**
## 🧩 Controls Library ```c++
// Create a complex text style
StellarX::ControlText myStyle;
myStyle.nHeight = 25; // Font height
myStyle.lpszFace = _T("Microsoft YaHei"); // Font
myStyle.color = RGB(255, 0, 0); // Red color
myStyle.nWeight = FW_BOLD; // Bold
myStyle.bUnderline = true; // Underline
### 1) Basic Controls // Apply to controls
myLabel->textStyle = myStyle;
myButton->textStyle = myStyle;
```
| Control | Header | Description | Key Points | ## 🧩 Complete Control Library
| ------- | ----------- | ----------------- | ------------------------------------------------------------ |
| 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 ### 1. Basic Controls
| Control | Header | Description | | Control | Header File | Description | Key Features |
| ------- | ---------- | ------------------------------------------------------------ | | :---------- | :---------- | :-------------------- | :----------------------------------------------------------- |
| Canvas | `Canvas.h` | Parent container with custom border/background; **built-in HBox/VBox auto layout** (v2.1.0) | | **Button** | `Button.h` | Multi-function button | Supports multiple modes/shapes/states, hover/click colors, custom callbacks |
| Window | `Window.h` | Top-level container with message loop and dispatch; **resizable** (v2.1.0) | | **Label** | `Label.h` | Text label | Supports transparent/opaque background, custom font styles |
| **TextBox** | `TextBox.h` | Input box/Display box | Supports input and read-only modes, integrates EasyX's `InputBox` |
### 3) Advanced Controls ### 2. Container Controls
| Control | Header | Description | Key Points | | Control | Header File | Description |
| ---------- | -------------- | ----------- | ------------------------------------------------------------ | | :--------- | :---------- | :----------------------------------------------------------- |
| Table | `Table.h` | Data grid | Paging/header/auto column width; fixed page-control overlap/ghosting (v2.1.0) | | **Canvas** | `Canvas.h` | Container control, can serve as parent container for other controls, supports custom borders and background. |
| Dialog | `Dialog.h` | Dialog | Modal/non-modal; auto layout; background save/restore | | **Window** | `Window.h` | Top-level window, ultimate container for all controls, responsible for message loop and dispatching. |
| 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 ### 3. Advanced Controls
| Control | Header | Description | Key Points | | Control | Header File | Description | Key Features |
| ---------- | -------------- | ------------------- | -------------------------------------------- | | :--------- | :---------- | :----------- | :----------------------------------------------------------- |
| MessageBox | `MessageBox.h` | Message-box factory | Static API; modal/non-modal; de-dup built in | | **Table** | `Table.h` | Data table | **Framework highlight feature**, supports paginated display, custom headers and data, automatic column width calculation, page turn buttons. |
| **Dialog** | `Dialog.h` | Dialog class | Implements complete dialog functionality, supports multiple button combinations and asynchronous result callbacks. Automatically handles layout, background save/restore and lifecycle management. |
------ **Table Control Example:**
## 📐 Layout Management (HBox/VBox) ```c++
// Create a table
auto myTable = std::make_unique<Table>(50, 50);
==Reserved, to be implemented== // Set headers
myTable->setHeaders({ "ID", "Name", "Age", "Occupation" });
------ // Add data rows
myTable->setData({ "1", "Zhang San", "25", "Engineer" });
myTable->setData({ "2", "Li Si", "30", "Designer" });
myTable->setData({ "3", "Wang Wu", "28", "Product Manager" });
## 🗂 Tabs (TabControl) // Set rows per page
myTable->setRowsPerPage(2);
- Tab strip (button group) + page container (`Canvas`) // Set table style
- For transparent themes: **background snapshot** switching in the page area to avoid ghosting myTable->textStyle.nHeight = 16;
- API: **see the API documentation** myTable->setTableBorder(RGB(50, 50, 50));
myTable->setTableBk(RGB(240, 240, 240));
------ // Add to window
mainWindow.addControl(std::move(myTable));
```
## ✂️ Single-line Text Truncation & Button Tooltip ### 4. Static Factory Class
- **Button truncation:** separate handling for CJK/Latin under MBCS; append `...` based on pixel-width threshold | Control | Header File | Description | Key Features |
- **Tooltip:** delayed show and auto-hide; default text = button text; customizable; uses control-level **background snapshot/restore** | :------------- | :------------- | :------------------- | :----------------------------------------------------------- |
| **MessageBox** | `MessageBox.h` | Dialog factory calls | Static method calls, no instantiation needed, automatically handles modal and non-modal logic differences, integrated into window's dialog management system, provides deduplication mechanism to prevent duplicate dialogs |
------ **MessageBox Usage Example:**
## 🧊 Transparent Background & Background Snapshots ```c++
// Modal message box (blocks until closed)
auto result = StellarX::MessageBox::ShowModal(
mainWindow,
"Confirm to perform this operation?",
"Confirmation",
StellarX::MessageBoxType::YesNo
);
- **General convention:** `captureBackground(rect)` before the first draw; `restoreBackground()` before hiding/covering if (result == StellarX::MessageBoxResult::Yes)
- **Table:** snapshot region **includes the header**; after page switch, restore immediately + redraw; paging controls centered {
// User selected "Yes"
}
------ // Non-modal message box (asynchronous callback)
StellarX::MessageBox::ShowAsync(
mainWindow,
"Operation completed",
"Notification",
StellarX::MessageBoxType::OK,
[](StellarX::MessageBoxResult result) {
// Asynchronously handle result
}
);
```
## Examples
- **Register Viewer (≈450 lines)** — An interactive 32-bit register visualization tool implemented based on StellarX (supports bit inversion, left/right shift, hex/decimal conversion, signed/unsigned toggle, binary grouping display).
Path: `examples/register-viewer/`
## 🔧 Advanced Topics & Best Practices ## 🔧 Advanced Topics & Best Practices
- Custom controls: inherit from `Control`, implement `draw()` / `handleEvent()` ### 1. Custom Controls
- 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
------ You can create custom controls by inheriting from the `Control` base class. Just implement the two pure virtual functions `draw()` and `handleEvent()`.
## ⚠️ Applicability & Limits ```c++
class MyCustomControl : public Control {
public:
MyCustomControl(int x, int y) : Control(x, y, 100, 100) {}
void draw() override {
saveStyle();
// Your custom drawing logic
setfillcolor(RGB(255, 100, 100));
fillrectangle(x, y, x + width, y + height);
restoreStyle();
}
bool handleEvent(const ExMessage& msg) override {
// Your custom event handling logic
if (msg.message == WM_LBUTTONDOWN &&
msg.x > x && msg.x < x + width &&
msg.y > y && msg.y < y + height) {
// Handle click
return true; // Event consumed
}
return false; // Event not consumed
}
bool IsVisible() const override { return true; }
bool model() const override { return false; }
};
```
- Not suitable for high-performance games or complex animation; re-verify metrics under extreme DPI ### 2. Layout Management
- No accessibility support yet
- Windows-only, not cross-platform
- For complex commercial front-ends, consider Qt / wxWidgets / ImGui / Electron
------ Current version of StellarX primarily uses **absolute positioning**. For simple layouts, you can achieve this by calculating coordinates. For complex layouts, consider:
- Nesting controls in `Canvas` to achieve relative positioning.
- Implementing simple flow layout or grid layout managers yourself.
### 3. Performance Optimization
- **Dirty Rectangle Rendering**: Implemented internally in the framework, controls set `dirty=true` when state changes, only redraw when necessary.
- **Image Resources**: Use `IMAGE` objects to load images, then reuse them to avoid multiple loads.
- **Reduce Operations in Loops**: Avoid heavy calculations in `draw()` and `handleEvent()`.
- **Event Consumption Mechanism**: Use event consumption return values appropriately to avoid unnecessary event propagation.
### 4. Dialog Usage Tips
- **Modal Dialogs**: Use `ShowModal` method, blocks current thread until dialog closes, suitable for important operations requiring user confirmation.
- **Non-modal Dialogs**: Use `ShowAsync` method, doesn't block main thread, suitable for informational prompts or background tasks.
- **Dialog Deduplication**: Framework has built-in non-modal dialog deduplication mechanism to prevent multiple dialogs for the same message from appearing simultaneously.
## ⚠️ Important Limitations & Suitable Scenarios
**StellarX framework's design goals are lightness, clarity, and teaching value, therefore it explicitly is NOT suitable for the following scenarios:**
- **High-performance games or complex animations**: Rendering is based on EasyX's CPU software rendering, performance is limited.
- **Applications requiring high DPI scaling**: Limited support for high DPI displays, interface may not display correctly.
- **Applications requiring accessibility features**: No support for screen readers and other assistive technologies.
- **Cross-platform applications**: Deeply dependent on Windows API and EasyX, cannot run directly on Linux/macOS.
- **Complex commercial software frontends**: Lacks advanced controls (like tree views, rich text boxes, tabs, advanced lists) and mature automatic layout managers.
**If you need to develop the aforementioned types of applications, please consider the following mature solutions:**
- **Qt**: Extremely powerful, cross-platform, suitable for large commercial applications.
- **wxWidgets**: Native appearance, cross-platform.
- **Dear ImGui**: Immediate mode GUI, very suitable for tools and debug interfaces.
- **Web Technology Stack (Electron/CEF)**: Suitable for scenarios requiring web technologies.
## 📜 License ## 📜 License
MIT (see `LICENSE`). This project uses the **MIT License**.
## 👥 Contributing Guidelines You are free to:
- Follow the existing C++ style - Use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the framework.
- New features should include examples and README updates - Use it for private or commercial projects.
- Self-test before submitting and explain the motivation for changes
- For bugs/ideas, please open an Issue The only requirement is:
- Please retain the original copyright notice in your projects.
See the [LICENSE](https://license/) file in the project root directory for details.
## 👥 Contribution Guide
We welcome all forms of contributions! If you want to contribute to the StellarX framework, please read the following guidelines:
1. **Code Style**: Please follow the existing Google C++ style guide (use spaces for indentation, braces on new lines, etc.).
2. **New Features**: Must provide **example code** and update relevant parts of this README document.
3. **Submitting PRs**: Please ensure your code is tested before submission and clearly describe your changes and motivation.
4. **Issue Reporting**: If you find a bug or have new ideas, welcome to submit Issues on GitHub.
Detailed contribution guide please refer to [CONTRIBUTING.md](CONTRIBUTING.en.md).
## 🙏 Acknowledgements ## 🙏 Acknowledgements
- Thanks to [EasyX](https://easyx.cn/) - Thanks to [EasyX Graphics Library](https://easyx.cn/) for providing the simple and easy-to-use graphics foundation for this project, making C++ GUI programming education possible.
- Thanks to developers who value **simplicity/efficiency/clarity** - Thanks to all developers pursuing the **concise, efficient, clear** coding philosophy, you are the inspiration for StellarX's birth.
------ ------
**Stars and seas, code as the vessel.** **Star Ocean Vast, Code as Boat.**
May `StellarX` become a reliable cornerstone for your exploration of the GUI world, whether for learning, teaching, or creating practical tools.
## 📞 Support & Feedback ## 📞 Support & Feedback
- See [examples/](examples/) If you encounter problems during use or have any suggestions:
- Read the [CHANGELOG](CHANGELOG.md / CHANGELOG.en.md)
- Submit an Issue on GitHub 1. Check [Example Code](examples/) for usage reference
2. Check [Changelog](CHANGELOG.en.md) to learn about latest changes
3. Submit Issues on GitHub repository to report problems
------
*StellarX Framework - Light as Dust, Bound by Stars*

546
README.md
View File

@@ -5,10 +5,11 @@
------ ------
![GitHub all releases](https://img.shields.io/github/downloads/Ysm-04/StellarX/total) ![GitHub all releases](https://img.shields.io/github/downloads/Ysm-04/StellarX/total)
[![Star GitHub Repo](https://img.shields.io/github/stars/Ysm-04/StellarX.svg?style=social&label=Star%20This%20Repo)](https://github.com/Ysm-04/StellarX) [![Star GitHub Repo](https://img.shields.io/github/stars/Ysm-04/StellarX.svg?style=social&label=Star%20This%20Repo)](https://github.com/Ysm-04/StellarX)
![Version](https://img.shields.io/badge/Version-2.2.0-brightgreen.svg) ![Version](https://img.shields.io/badge/Version-2.0.0-brightgreen.svg)
![Download](https://img.shields.io/badge/Download-2.2.0_Release-blue.svg) ![Download](https://img.shields.io/badge/Download-2.0.0_Release-blue.svg)
![C++](https://img.shields.io/badge/C++-17+-00599C?logo=cplusplus&logoColor=white) ![C++](https://img.shields.io/badge/C++-17+-00599C?logo=cplusplus&logoColor=white)
![Windows](https://img.shields.io/badge/Platform-Windows-0078D6?logo=windows) ![Windows](https://img.shields.io/badge/Platform-Windows-0078D6?logo=windows)
@@ -17,287 +18,438 @@
![Architecture](https://img.shields.io/badge/Architecture-Modular%20OOP-brightgreen) ![Architecture](https://img.shields.io/badge/Architecture-Modular%20OOP-brightgreen)
![CMake](https://img.shields.io/badge/Build-CMake-064F8C?logo=cmake) ![CMake](https://img.shields.io/badge/Build-CMake-064F8C?logo=cmake)
> **「繁星为界,轻若尘埃」** —— 一个为 Windows 平台打造的、极致轻量级、高度模块化的 C++ 原生 GUI 框架。 > **「繁星为界,轻若尘埃」** —— 一个为Windows平台打造的、极致轻量级、高度模块化的C++原生GUI框架。
`星垣 (StellarX)` 反对臃肿,拒绝动辄数百 MB 的依赖、漫长编译与高门槛学习曲线,回归本质:精简代码、清晰架构与高效率,解决桌面应用开发的核心需求。 `星垣 (StellarX)` 诞生于对现代GUI框架"过度臃肿"的反抗。它拒绝动辄数百MB的依赖、漫长编译时间和复杂的学习曲线,选择回归本质:用最精简代码、清晰架构和最高的效率,解决桌面应用开发的核心需求。
是一个**教学级、工具级**框架,帮助开发者深入理解 GUI 原理,并快速构建轻量工具。 是一个**纯粹的教学级、工具级框架**,旨在让开发者深入理解GUI原理并快速构建轻量级Windows工具。
---
## 🆕v2.2.1v2.2.0修复版)
- 解决了使用Canvas和TabControl容器时出现频闪问题
- 修复了Dialog对话框关闭时概率出边边框残留和功能按钮残留问题
详情参考[更新日志](CHANGELOG.md)
## V2.2.0 有何变化
- **新增 TabControl 控件,实现多页面选项卡界面:** 通过 `TabControl` 可以轻松创建选项卡式布局,支持页签在上下左右排列、点击切换显示不同内容页面。适用于设置面板、多视图切换等场景。
- **控件显隐与布局响应能力增强:** 现在所有控件都可以使用统一接口动态隐藏或显示(`setIsVisible`),容器控件隐藏时其内部子控件会自动随之隐藏/显示。与此同时,引入控件对窗口尺寸变化的响应机制(`onWindowResize`),窗口拉伸后界面各元素可协调更新,杜绝拉伸过程中出现残影或错位。
- **文本样式机制完善:** Label 控件改用统一的文本样式结构 `ControlText`,开发者可方便地设置字体、颜色、大小等属性来定制 Label 的外观替代旧接口更加灵活。Button 的 Tooltip 提示也支持更丰富的定制和针对切换状态的不同提示文本。
- **其他改进:** 框架底层的对话框管理增加了防重复弹出相同提示的机制,修复了一些细节 Bug 并优化了刷新效率,进一步提升了稳定性。
详见 `CHANGELOG.md / CHANGELOG.en.md` 获取完整更新列表。
--- ---
## 📦 项目结构与设计哲学 ## 📦 项目结构与设计哲学
星垣采用经典 **OOP****模块化** 设计,结构清晰: 星垣框架采用经典的**面向对象**和**模块化**设计,项目结构清晰规范
```markdown ```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 # 文本框控件
│ ├── TabControl.h #v2.2.0 │ ├── Canvas.h # 画布容器
│ ├── Canvas.h │ ├── Dialog.h # 对话框控件v2.0.0新增)
│ ├── Dialog.h │ ├── MessageBox.h # 消息框工厂v2.0.0新增)
── MessageBox.h ── Table.h # 表格控件
│ └── Table.h ├── src/ # 源文件目录
├── 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
│ ├── TabControl.cpp #v2.2.0 │ ├── Table.cpp
│ ├── Table.cpp │ ├── Dialog.cpp # v2.0.0新增
── Dialog.cpp ── MessageBox.cpp # v2.0.0新增
│ └── MessageBox.cpp ├── examples/ # 示例代码目录
├── examples/ │ └── demo.cpp # 基础演示
│ └── demo.cpp ├── docs/ # 文档目录
├── docs/ │ └── CODE_OF_CONDUCT.md # 行为准则
│ └── CODE_OF_CONDUCT.md ├── CMakeLists.txt # CMake 构建配置
├── CMakeLists.txt ├── CONTRIBUTING.md # 贡献指南
├── CONTRIBUTING.md ├── CHANGELOG.md # 更新日志
├── CHANGELOG.md ├── Doxyfile # Doxygen 配置
├── CHANGELOG.en.md ├── LICENSE # MIT 许可证
── Doxyfile ── README.md # 项目说明
├── LICENSE
├──API 文档.md
├──API Documentation.en.md
└── README.md
``` ```
**设计理念:** **设计理念:**
1. **单一职责SRP**每个类/文件只一件事。 1. **单一职责原则 (SRP)**: 每个类/文件只负责一件事。
2. **依赖倒置DIP**高层模块依赖抽象(`Control`,而非具体控件 2. **依赖倒置原则 (DIP)**: 高层模块(如`Window`)不依赖低层模块(如`Button`),二者都依赖其抽象(`Control`)。
3. **开闭原则OCP**继承 `Control`扩展新控件,无需修改有代码。 3. **开闭原则 (OCP)**: 通过继承`Control`基类,可以轻松扩展新控件,无需修改有代码。
4. **一致性**所有控件统一 `draw()` / `handleEvent()` 接口。 4. **一致性**: 所有控件共享统一`draw()``handleEvent()`接口。
## 🚀 核心特性 ## 🚀 核心特性
- **极致轻量**:除 EasyX 外无外部重量级依赖 - **极致轻量**: 核心库编译后仅 ~12MB无任何外部依赖。生成的应用程序小巧玲珑
- **模块清晰**`CoreTypes.h` 统一类型与枚举 - **清晰的模块化架构**: 使用`CoreTypes.h`统一管理所有类型,消除重复定义,极大提升可维护性
- **原生性能**EasyX + Win32执行高效、内存占用(常见 <10MB - **原生C++性能**: 直接基于EasyXWin32 API提供接近原生的执行效率,内存占用极低(通常<10MB
- **控件齐全**Button、Label、TextBoxCanvas、Table、Dialog、MessageBox、**TabControl** - **完整的控件体系**: 按钮Button、标签Label、文本框TextBox)、画布(Canvas、表格Table、对话框Dialog与消息框工厂MessageBox
- **高度自定义**颜色、形状(矩形/圆角/圆/椭圆)填充、字体等皆有枚举配置,易于切换 - **高度可定制化**: 从控件颜色、形状(矩形圆角、圆形、椭圆)填充模式、字体样式,均有详尽枚举支持,可轻松定制
- **简直观 API**OOP 设计,接口语义明确、调用友好,代码即文档 - **简直观API**: 采用经典的面向对象设计,代码即文档,学习成本极低
- **标准工程结构**include/src 分离,支持 CMake 构建,方便集成到现有项目或开箱即用。 - **标准项目结构**: 采用标准的include/src分离结构支持CMake构建易于集成和使用。
- **增强的事件系统**: v2.0.0引入事件消费机制,所有`handleEvent`方法返回`bool`表示是否消费事件,支持更精细的事件传播控制。
- **对话框系统**: 新增完整的对话框支持,包括模态和非模态对话框,自动处理背景保存和恢复。
------ ## ⚡ 快速开始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如 VS 2019+ - **编译器**: 支持C++17的编译器 (如: **Visual Studio 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
1. 下载 EasyX 最新版 ### 方法一使用CMake构建推荐
2. 按 VS 版本安装匹配组件
3. 框架会自动链接,无需额外配置
### CMake 构建(推荐) 1. **克隆项目**:
```bash
git clone https://github.com/Ysm-04/StellarX.git
cd StellarX
```
```bash 2. **生成构建系统**:
git clone https://github.com/Ysm-04/StellarX.git ```bash
cd StellarX mkdir build
mkdir build && cd build cd build
cmake .. cmake ..
cmake --build . ```
./examples/Demo
```
### 手动集成 3. **编译项目**:
```bash
cmake --build .
```
- 拷贝 `include``src` 4. **运行示例**:
- 配置头文件搜索路径:`include/StellarX/` ```bash
- 将全部 `.cpp` 加入工程 ./examples/Demo
```
### 第一个可拉伸窗口 ### 方法二:手动集成到现有项目
```c++ 1. **将include和src目录复制**到您的项目中
2. **配置包含路径**,确保编译器可以找到`include/StellarX/`目录
3. **将所有.cpp文件**添加到您的项目中编译
### 创建你的第一个星垣应用
```cpp
// 只需包含这一个头文件即可使用所有功能
#include "StellarX.h" #include "StellarX.h"
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) // 程序入口点请使用WinMain以获得更好的兼容性
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{ {
//默认启动拉伸,当前尺寸为最小尺寸 // 1. 创建一个640x480的窗口背景为白色标题为"我的应用"
Window mainWindow(800, 600, 0, RGB(255,255,255), "我的星垣应用"); Window mainWindow(640, 480, 0, RGB(255, 255, 255), "我的第一个星垣应用");
// 2. 创建一个按钮 (使用智能指针管理)
auto myButton = std::make_unique<Button>(
250, 200, 140, 40, // x, y, 宽度, 高度
"点击我", // 按钮文本
StellarX::ButtonMode::NORMAL,
StellarX::ControlShape::ROUND_RECTANGLE
);
// 3. 为按钮设置点击事件使用Lambda表达式
myButton->setOnClickListener([&mainWindow]() {
// 使用消息框工厂创建模态对话框
auto result = StellarX::MessageBox::ShowModal(
mainWindow,
"欢迎使用星垣GUI\r\n作者我在人间做废物",
"问候",
StellarX::MessageBoxType::OKCancel
);
// 处理对话框结果
if (result == StellarX::MessageBoxResult::OK) {
// 用户点击了确定按钮
}
});
// 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(); mainWindow.draw();
// 添加你的控件... // 7. 进入消息循环,等待用户交互
// mainWindow.addControl(std::move(btn));
mainWindow.runEventLoop(); mainWindow.runEventLoop();
return 0; return 0;
} }
``` ```
> 实现要点:在 `WM_PAINT` 期进行**整窗背景绘制**(纯色/图片),并配合 EasyX 批量绘制抑制频闪与黑边 1. **编译并运行!** 您将看到一个带有蓝色圆角按钮的窗口,点击它将会弹出消息框
------ ## 📚 核心类型详解 (`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`, ... | | **`MessageBoxType`** | 消息框类型 | `OK`, `OKCancel`, `YesNo`, `YesNoCancel`, `RetryCancel`, `AbortRetryIgnore` |
| `MessageBoxResult` | 结果 | `OK`, `Cancel`, `Yes`, `No`, `Abort`, `Retry`, `Ignore` | | **`MessageBoxResult`** | 消息框结果 | `OK`, `Cancel`, `Yes`, `No`, `Abort`, `Retry`, `Ignore` |
| `TabPlacement` | 页签位置 | `Top`,`Bottom`,`Left`,`Right` |
### 结构体 ### 结构体 (Structs)
| 结构体 | 描述 | | 结构体 | 描述 |
| -------------- | -------------------------------------- | | :----------------- | :----------------------------------------------------------- |
| `ControlText` | 字体/字号/颜色/粗体/斜体/下划线/删除线 | | **`ControlText`** | 封装了所有文本样式属性,包括字体、大小、颜色粗体斜体下划线删除线等。 |
| `RouRectangle` | 圆角矩形的角椭圆尺寸 | | **`RouRectangle`** | 定义圆角矩形的角椭圆尺寸,包含宽度和高度属性。 |
------ **使用示例:**
## 🧩 控件库 cpp
### 1) 基础控件 ```c++
// 创建一个复杂的文本样式
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;
| Button | `Button.h` | 多功能按钮 | 形状/模式、悬停/点击色、回调,**单行截断** + **Tooltip**v2.1.0 | myButton->textStyle = myStyle;
| Label | `Label.h` | 文本标签 | 透明/不透明背景,自定义字体 | ```
| TextBox | `TextBox.h` | 输入/显示框 | 输入/只读,整合 EasyX `InputBox` |
### 2) 容器控件
| 控件 | 头文件 | 描述 |
| ------ | ---------- | ------------------------------------------------------------ |
| Canvas | `Canvas.h` | 父容器,自定义边框/背景,**内置 HBox/VBox 自动布局**v2.1.0 |
| Window | `Window.h` | 顶层容器,消息循环与分发,**可拉伸**v2.1.0 |
### 3) 高级控件
| 控件 | 头文件 | 描述 | 关键点 |
| ---------- | -------------- | -------- | ------------------------------------------------------------ |
| Table | `Table.h` | 数据表格 | 分页/表头/列宽自动、翻页控件重叠/透明叠影已修复v2.1.0 |
| Dialog | `Dialog.h` | 对话框 | 支持模态/非模态,自动布局与背景保存/恢复 |
| TabControl | `TabControl.h` | 选项卡 | 支持一键添加“页签+页”对pair也可以单独对某页添加子控件采用相对坐标 |
### 4) 静态工厂
| 控件 | 头文件 | 描述 | 关键点 |
| ---------- | -------------- | ---------- | ----------------------------------- |
| MessageBox | `MessageBox.h` | 消息框工厂 | 静态 API支持模态/非模态;内置去重 |
------
## 📐 布局管理HBox/VBox
==预留,待实现==
------ ## 🧩 控件库大全
## 🗂 选项卡TabControl ### 1. 基础控件
- 页签条(按钮组) + 页面容器(`Canvas` | 控件 | 头文件 | 描述 | 关键特性 |
- 透明主题:页面区域**背景快照**切换,避免叠影 | :---------- | :---------- | :------------ | :------------------------------------------------------ |
- API **查看API文档** | **Button** | `Button.h` | 多功能按钮 | 支持多种模式/形状/状态,可设置悬停/点击颜色,自定义回调 |
| **Label** | `Label.h` | 文本标签 | 支持背景透明/不透明,自定义字体样式 |
| **TextBox** | `TextBox.h` | 输入框/显示框 | 支持输入和只读模式集成EasyX的`InputBox` |
------ ### 2. 容器控件
## ✂️ 文本单行截断 & Button Tooltip | 控件 | 头文件 | 描述 |
| :--------- | :--------- | :------------------------------------------------------- |
| **Canvas** | `Canvas.h` | 容器控件,可作为其他控件的父容器,支持自定义边框和背景。 |
| **Window** | `Window.h` | 顶级窗口,所有控件的最终容器,负责消息循环和调度。 |
- **按钮截断**:多字节字符集下**中/英分治**,基于像素宽度阈值追加 `...` ### 3. 高级控件
- **Tooltip**:延时出现、自动隐藏;默认文字=按钮文本,可自定义;使用控件级**背景快照/恢复**
------ | 控件 | 头文件 | 描述 | 关键特性 |
| :--------- | :--------- | :------- | :----------------------------------------------------------- |
| **Table** | `Table.h` | 数据表格 | **框架功能亮点**,支持分页显示、自定义表头和数据、自动计算列宽、翻页按钮。 |
| **Dialog** | `Dialog.h` | 对话框类 | 实现完整的对话框功能,支持多种按钮组合和异步结果回调。自动处理布局、背景保存恢复和生命周期管理。 |
## 🧊 透明背景与背景快照 **表格控件示例:**
- **通用约定**:首绘前 `captureBackground(rect)`,隐藏/覆盖前 `restoreBackground()` cpp
- **Table**:快照区域**包含表头**;翻页后立即恢复 + 重绘,分页控件整体居中
------ ```c++
// 创建一个表格
auto myTable = std::make_unique<Table>(50, 50);
// 设置表头
myTable->setHeaders({ "ID", "姓名", "年龄", "职业" });
// 添加数据行
myTable->setData({ "1", "张三", "25", "工程师" });
myTable->setData({ "2", "李四", "30", "设计师" });
myTable->setData({ "3", "王五", "28", "产品经理" });
// 设置每页显示2行
myTable->setRowsPerPage(2);
// 设置表格样式
myTable->textStyle.nHeight = 16;
myTable->setTableBorder(RGB(50, 50, 50));
myTable->setTableBk(RGB(240, 240, 240));
// 添加到窗口
mainWindow.addControl(std::move(myTable));
```
### 4. 静态工厂类
| 控件 | 头文件 | 描述 | 关键特性 |
| :------------- | :------------- | :--------------- | :----------------------------------------------------------- |
| **MessageBox** | `MessageBox.h` | 对话框的工厂调用 | 静态方法调用,无需实例化,自动处理模态和非模态的逻辑差异集成到窗口的对话框管理系统中提供去重机制防止重复对话框 |
**消息框使用示例:**
cpp
```c++
// 模态消息框(阻塞直到关闭)
auto result = StellarX::MessageBox::ShowModal(
mainWindow,
"确认要执行此操作吗?",
"确认",
StellarX::MessageBoxType::YesNo
);
if (result == StellarX::MessageBoxResult::Yes)
{
// 用户选择了"是"
}
// 非模态消息框(异步回调)
StellarX::MessageBox::ShowAsync(
mainWindow,
"操作已完成",
"提示",
StellarX::MessageBoxType::OK,
[](StellarX::MessageBoxResult result) {
// 异步处理结果
}
);
```
## 示例
- **寄存器查看器 (≈450 行)** — 一个基于 StellarX 实现的交互式 32 位寄存器可视化工具(支持位取反、左/右移、十六进制/十进制转换、带有符号/无符号切换、二进制分组显示)。
路径:`examples/register-viewer/`
## 🔧 高级主题与最佳实践 ## 🔧 高级主题与最佳实践
- 自定义控件:继承 `Control`,实现 `draw()` / `handleEvent()` ### 1. 自定义控件
- 性能:
- **脏矩形**:状态改变时置 `dirty=true`,按需重绘
- **避免额外 `cleardevice()`**:背景已由 `WM_PAINT` 统一处理
- 绘制前确保 `SetWorkingImage(nullptr)` 将输出落到屏幕
- 事件消费:处理后返回 `true` 终止传播
------ 您可以通过继承`Control`基类来创建自定义控件。只需实现`draw()`和`handleEvent()`两个纯虚函数即可。
## ⚠️ 适用与限制 cpp
- 不适合高性能游戏/复杂动画;极端 DPI 需复核度量 ```c++
- 暂无无障碍能力 class MyCustomControl : public Control {
- Windows 专用,不跨平台 public:
- 复杂商业前端建议用 Qt / wxWidgets / ImGui / Electron MyCustomControl(int x, int y) : Control(x, y, 100, 100) {}
void draw() override {
saveStyle();
// 您的自定义绘制逻辑
setfillcolor(RGB(255, 100, 100));
fillrectangle(x, y, x + width, y + height);
restoreStyle();
}
bool handleEvent(const ExMessage& msg) override {
// 您的自定义事件处理逻辑
if (msg.message == WM_LBUTTONDOWN &&
msg.x > x && msg.x < x + width &&
msg.y > y && msg.y < y + height) {
// 处理点击
return true; // 事件已消费
}
return false; // 事件未消费
}
bool IsVisible() const override { return true; }
bool model() const override { return false; }
};
```
------
## 📜 许可证
MIT见 `LICENSE`)。 ### 2. 布局管理
## 👥 贡献指南 当前版本星垣主要采用**绝对定位**。对于简单布局,您可以通过计算坐标来实现。对于复杂布局,可以考虑:
- 遵循现有 C++ 风格 - 在`Canvas`中嵌套控件,实现相对定位。
- 新特性需附示例与 README 更新 - 自行实现简单的流式布局或网格布局管理器。
- 提交前请自测,并说明变更动机
- Bug/想法请提 Issue
## 🙏 致谢 ### 3. 性能优化
- 感谢 [EasyX](https://easyx.cn/) - **脏矩形渲染**: 框架内部已实现,控件状态改变时`dirty=true`,仅在需要时重绘。
- 感谢推崇**简洁/高效/清晰**的开发者 - **图像资源**: 使用`IMAGE`对象加载图片后,可重复使用,避免多次加载。
- **减少循环内操作**: 在`draw()`和`handleEvent()`中避免进行重型计算。
- **事件消费机制**: 合理使用事件消费返回值,避免不必要的事件传播。
### 4. 对话框使用技巧
- **模态对话框**: 使用`ShowModal`方法,会阻塞当前线程直到对话框关闭,适合需要用户确认的重要操作。
- **非模态对话框**: 使用`ShowAsync`方法,不会阻塞主线程,适合提示性信息或后台任务。
- **对话框去重**: 框架内置了非模态对话框去重机制,防止同一消息的多个对话框同时出现。
## ⚠️ 重要限制与适用场景
**星垣框架的设计目标是轻便、清晰和教学价值,因此它明确** **不适用于** **以下场景:**
- **高性能游戏或复杂动画**: 渲染基于EasyX的CPU软件渲染性能有限。
- **需要高DPI缩放的应用**: 对高DPI显示器的支持有限界面可能显示不正确。
- **需要无障碍功能的应用**: 未提供对屏幕阅读器等辅助技术的支持。
- **跨平台应用**: 深度依赖Windows API和EasyX无法直接在Linux/macOS上运行。
- **复杂的商业软件前端**: 缺乏高级控件(如树形图、富文本框、选项卡、高级列表等)和成熟的自动布局管理器。
**如果您需要开发上述类型的应用,请考虑使用以下成熟方案:**
- **Qt**: 功能极其强大,跨平台,适合大型商业应用。
- **wxWidgets**: 原生外观,跨平台。
- **Dear ImGui**: 即时模式GUI非常适合工具和调试界面。
- **Web技术栈 (Electron/CEF)**: 适合需要Web技术的场景。
## 📜 许可证 (License)
本项目采用 **MIT 许可证**。
您可以自由地:
- 使用、复制、修改、合并、出版发行、散布、再授权及销售本框架的副本。
- 将其用于私人或商业项目。
唯一要求是:
- 请在您的项目中保留原始的版权声明。
详见项目根目录的 [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/) 如果您在使用过程中遇到问题或有任何建议:
- 查阅 [更新日志](CHANGELOG.md)[CHANGELOG](CHANGELOG.en.md)
- 在 GitHub 提交 Issue 1. 查看 [示例代码](examples/) 获取使用参考
2. 查阅 [更新日志](CHANGELOG.md) 了解最新变化
3. 在GitHub仓库提交Issue反馈问题
------
*星垣框架 - 轻若尘埃,繁星为界*

View File

@@ -171,10 +171,20 @@ public:
//设置提示框位置偏移 //设置提示框位置偏移
void setTooltipOffset(int dx, int dy) { tipOffsetX = dx; tipOffsetY = dy; } void setTooltipOffset(int dx, int dy) { tipOffsetX = dx; tipOffsetY = dy; }
//设置提示框样式 //设置提示框样式
void setTooltipStyle(COLORREF text, COLORREF bk, bool transparent); void setTooltipStyle(COLORREF text, COLORREF bk, bool transparent)
{
tipLabel.setTextColor(text);
tipLabel.setTextBkColor(bk);
tipLabel.setTextdisap(transparent);
}
//设置提示框文本 //设置提示框文本
void setTooltipText(const std::string& s){ tipTextClick = s; tipUserOverride = true; } void setTooltipText(const std::string& s){ tipTextClick = s; tipUserOverride = true; }
void setTooltipTextsForToggle(const std::string& onText, const std::string& offText); void setTooltipTextsForToggle(const std::string& onText, const std::string& offText)
{
tipTextOn = onText;
tipTextOff = offText;
tipUserOverride = true;
}
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);

View File

@@ -56,13 +56,7 @@ 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: private:
//用来检查对话框是否模态,此控件不做实现 //用来检查对话框是否模态,此控件不做实现
bool model() const override { return false; }; bool model() const override { return false; };

View File

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

View File

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

View File

@@ -20,7 +20,7 @@
#pragma once #pragma once
#include"StellarX.h" #include"StellarX.h"
#define closeButtonWidth 30 //关闭按钮宽度 #define closeButtonWidth 23 //关闭按钮宽度
#define closeButtonHeight 20 //关闭按钮高度 同时作为对话框标题栏高度 #define closeButtonHeight 20 //关闭按钮高度 同时作为对话框标题栏高度
#define functionButtonWidth 70 //按钮宽度 #define functionButtonWidth 70 //按钮宽度
#define functionButtonHeight 30 //按钮高度 #define functionButtonHeight 30 //按钮高度
@@ -29,7 +29,7 @@
#define buttonAreaHeight 50 //按钮区域高度 #define buttonAreaHeight 50 //按钮区域高度
#define titleToTextMargin 10 //标题到文本的距离 #define titleToTextMargin 10 //标题到文本的距离
#define textToBorderMargin 10 //文本到边框的距离 #define textToBorderMargin 10 //文本到边框的距离
#define BorderWidth 3 //边框宽度
class Dialog : public Canvas class Dialog : public Canvas
{ {
Window& hWnd; //窗口引用 Window& hWnd; //窗口引用
@@ -37,6 +37,7 @@ class Dialog : public Canvas
int textWidth = 0; //文本宽度 int textWidth = 0; //文本宽度
int textHeight = 0; //文本高度 int textHeight = 0; //文本高度
int buttonNum = 0; // 按钮数量 int buttonNum = 0; // 按钮数量
int BorderWidth = 2; //边框宽度
StellarX::MessageBoxType type = StellarX::MessageBoxType::OK; //对话框类型 StellarX::MessageBoxType type = StellarX::MessageBoxType::OK; //对话框类型
std::string titleText = "提示"; //标题文本 std::string titleText = "提示"; //标题文本
@@ -123,14 +124,11 @@ private:
void getTextSize(); void getTextSize();
//初始化对话框尺寸 //初始化对话框尺寸
void initDialogSize(); void initDialogSize();
void saveBackground(int x, int y, int w, int h)override;
void restBackground()override;
// 清除所有控件 // 清除所有控件
void clearControls(); void clearControls();
//创建对话框按钮 //创建对话框按钮
std::unique_ptr<Button> createDialogButton(int x, int y, const std::string& text); std::unique_ptr<Button> createDialogButton(int x, int y, const std::string& text);
void requestRepaint(Control* parent) override;
}; };

View File

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

View File

@@ -1,68 +0,0 @@
/*******************************************************************************
* @类: 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;
};

View File

@@ -22,8 +22,11 @@
class Label : public Control class Label : public Control
{ {
std::string text; //标签文本 std::string text; //标签文本
COLORREF textBkColor; //标签背景颜色 COLORREF textColor; //标签文本颜色
bool textBkDisap = false; //标签背景是否透明 COLORREF textBkColor; //标签背景颜色
bool textBkDisap = false; //标签背景是否透明
//标签事件处理(标签无事件)不实现具体代码 //标签事件处理(标签无事件)不实现具体代码
bool handleEvent(const ExMessage& msg) override { return false; } bool handleEvent(const ExMessage& msg) override { return false; }
@@ -39,6 +42,8 @@ public:
void hide(); void hide();
//设置标签背景是否透明 //设置标签背景是否透明
void setTextdisap(bool key); void setTextdisap(bool key);
//设置标签文本颜色
void setTextColor(COLORREF color);
//设置标签背景颜色 //设置标签背景颜色
void setTextBkColor(COLORREF color); void setTextBkColor(COLORREF color);
//设置标签文本 //设置标签文本

View File

@@ -22,34 +22,6 @@
#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 // 按钮垂直 paddinginitButton
#define TABLE_BTN_TEXT_PAD_V 8 // 计算页脚高度时的按钮文字垂直 paddinginitTextWaH
#define TABLE_FOOTER_PAD 16 // 页脚额外高度(底部留白)
#define TABLE_FOOTER_BLANK 8 // 页脚顶部留白
#define TABLE_PAGE_TEXT_OFFSET_X (-40) // 页码文本的临时水平修正
// === Table defaults (theme) ===
#define TABLE_DEFAULT_ROWS_PER_PAGE 5
#define TABLE_DEFAULT_BORDER_WIDTH 1
#define TABLE_DEFAULT_BORDER_COLOR RGB(0,0,0)
#define TABLE_DEFAULT_BG_COLOR RGB(255,255,255)
// === Strings (i18n ready) ===
#define TABLE_STR_PREV "上一页"
#define TABLE_STR_NEXT "下一页"
#define TABLE_STR_PAGE_PREFIX "第"
#define TABLE_STR_PAGE_MID "页/共"
#define TABLE_STR_PAGE_SUFFIX "页"
class Table :public Control class Table :public Control
{ {
private: private:
@@ -57,12 +29,12 @@ private:
std::vector<std::string> headers; // 表格表头 std::vector<std::string> headers; // 表格表头
std::string pageNumtext = "页码标签"; // 页码标签文本 std::string pageNumtext = "页码标签"; // 页码标签文本
int tableBorderWidth = TABLE_DEFAULT_BORDER_WIDTH; // 边框宽度 int tableBorderWidth = 1; // 边框宽度
std::vector<int> colWidths; // 每列的宽度 std::vector<int> colWidths; // 每列的宽度
std::vector<int> lineHeights; // 每行的高度 std::vector<int> lineHeights; // 每行的高度
int rowsPerPage = TABLE_DEFAULT_ROWS_PER_PAGE; // 每页显示的行数 int rowsPerPage = 5; // 每页显示的行数
int currentPage = 1; // 当前页码 int currentPage = 1; // 当前页码
int totalPages = 1; // 总页数 int totalPages = 1; // 总页数
@@ -82,8 +54,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 = TABLE_DEFAULT_BORDER_COLOR; // 表格边框颜色 COLORREF tableBorderClor = RGB(0, 0, 0); // 表格边框颜色
COLORREF tableBkClor = TABLE_DEFAULT_BG_COLOR; // 表格背景颜色 COLORREF tableBkClor = RGB(255, 255, 255); // 表格背景颜色
void initTextWaH(); //初始化文本像素宽度和高度 void initTextWaH(); //初始化文本像素宽度和高度
void initButton(); //初始化翻页按钮 void initButton(); //初始化翻页按钮

View File

@@ -76,8 +76,6 @@ public:
COLORREF getBkcolor() const; COLORREF getBkcolor() const;
//获取窗口背景图片 //获取窗口背景图片
IMAGE* getBkImage() const; IMAGE* getBkImage() const;
//获取窗口背景图片文件名
std::string getBkImageFile() const;
//获取控件管理 //获取控件管理
std::vector<std::unique_ptr<Control>>& getControls(); std::vector<std::unique_ptr<Control>>& getControls();

View File

@@ -128,23 +128,8 @@ static std::string ellipsize_cjk_pref(const std::string& text, int maxW, const c
return head; 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;
@@ -157,7 +142,7 @@ void Button::initButton(const std::string text, StellarX::ButtonMode mode, Stell
// === Tooltip 默认:文本=按钮文本;白底黑字;不透明;用当前按钮字体样式 === // === Tooltip 默认:文本=按钮文本;白底黑字;不透明;用当前按钮字体样式 ===
tipTextClick = tipTextOn = tipTextOff = this->text; tipTextClick = tipTextOn = tipTextOff = this->text;
tipLabel.setText(tipTextClick); tipLabel.setText(tipTextClick);
tipLabel.textStyle.color = (RGB(167, 170, 172)); tipLabel.setTextColor(RGB(167, 170, 172));
tipLabel.setTextBkColor(RGB(255, 255, 255)); tipLabel.setTextBkColor(RGB(255, 255, 255));
tipLabel.setTextdisap(false); tipLabel.setTextdisap(false);
tipLabel.textStyle = this->textStyle; // 复用按钮字体样式 tipLabel.textStyle = this->textStyle; // 复用按钮字体样式
@@ -173,108 +158,105 @@ Button::~Button()
void Button::draw() void Button::draw()
{ {
if (!dirty || !show)return; if (dirty && show)
//保存当前样式和颜色
saveStyle();
if (StellarX::ButtonMode::DISABLED == mode) //设置禁用按钮色
{ {
setfillcolor(DISABLEDCOLOUR); //保存当前样式和颜色
textStyle.bStrikeOut = true; saveStyle();
}
else
{
// 点击状态优先级最高,然后是悬停状态,最后是默认状态
COLORREF col = click ? buttonTrueColor : (hover ? buttonHoverColor : buttonFalseColor);
setfillcolor(col);
}
//
//设置字体背景色透明
setbkmode(TRANSPARENT);
//边框颜色
setlinecolor(buttonBorderColor);
//设置字体颜 if (StellarX::ButtonMode::DISABLED == mode) //设置禁用按钮
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)
{ {
this->oldtext_width = this->text_width = textwidth(LPCTSTR(this->cutText.c_str())); setfillcolor(DISABLEDCOLOUR);
this->oldtext_height = this->text_height = textheight(LPCTSTR(this->cutText.c_str())); textStyle.bStrikeOut = true;
} }
else else
{ {
this->oldtext_width = this->text_width = textwidth(LPCTSTR(this->text.c_str())); // 点击状态优先级最高,然后是悬停状态,最后是默认状态
this->oldtext_height = this->text_height = textheight(LPCTSTR(this->text.c_str())); 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)
{
this->oldtext_width = this->text_width = textwidth(LPCTSTR(this->cutText.c_str()));
this->oldtext_height = this->text_height = textheight(LPCTSTR(this->cutText.c_str()));
}
else
{
this->oldtext_width = this->text_width = textwidth(LPCTSTR(this->text.c_str()));
this->oldtext_height = this->text_height = textheight(LPCTSTR(this->text.c_str()));
}
}
//设置按钮填充模式
setfillstyle((int)buttonFillMode, (int)buttonFillIma, buttonFileIMAGE);
//根据按钮形状绘制
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; //标记按钮不需要重绘
} }
//设置按钮填充模式
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; //标记按钮不需要重绘
} }
// 处理鼠标事件,检测点击和悬停状态 // 处理鼠标事件,检测点击和悬停状态
// 根据按钮模式和形状进行不同的处理 // 根据按钮模式和形状进行不同的处理
@@ -416,11 +398,9 @@ bool Button::handleEvent(const ExMessage& msg)
// 如果需要重绘,立即执行 // 如果需要重绘,立即执行
if (dirty) if (dirty)
requestRepaint(parent); draw();
if(tipEnabled && tipVisible)
if (tipEnabled && tipVisible)
tipLabel.draw(); tipLabel.draw();
return consume; return consume;
} }
@@ -440,11 +420,8 @@ 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; // 标记需要重绘
} }
void Button::setROUND_RECTANGLEwidth(int width) void Button::setROUND_RECTANGLEwidth(int width)
@@ -555,7 +532,7 @@ void Button::setButtonClick(BOOL click)
flushmessage(EX_MOUSE | EX_KEY); flushmessage(EX_MOUSE | EX_KEY);
} }
if (dirty) if (dirty)
requestRepaint(parent); draw();
} }
@@ -685,6 +662,7 @@ void Button::hideTooltip()
void Button::refreshTooltipTextForState() void Button::refreshTooltipTextForState()
{ {
if (!tipUserOverride) return;
if (tipUserOverride) return; // 用户显式设置过 tipText保持不变 if (tipUserOverride) return; // 用户显式设置过 tipText保持不变
if(mode==StellarX::ButtonMode::NORMAL) if(mode==StellarX::ButtonMode::NORMAL)
tipLabel.setText(tipTextClick); tipLabel.setText(tipTextClick);

View File

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

View File

@@ -42,18 +42,6 @@ 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::saveStyle() void Control::saveStyle()
@@ -77,25 +65,11 @@ void Control::restoreStyle()
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) void Control::saveBackground(int x, int y, int w, int h)
{ {
if (w <= 0 || h <= 0) return; if (w <= 0 || h <= 0) return;
saveBkX = x; saveBkY = y; saveWidth = w; saveHeight = h; saveBkX = x; saveBkY = y; saveWidth = w; saveHeight = h;
if (saveBkImage) if (saveBkImage)
{ {
//尺寸变了才重建,避免反复 new/delete //尺寸变了才重建,避免反复 new/delete
@@ -131,6 +105,10 @@ void Control::discardBackground()
void Control::updateBackground() void Control::updateBackground()
{ {
restBackground(); if (saveBkImage)
discardBackground(); {
delete saveBkImage;
saveBkImage = nullptr;
}
hasSnap = false; saveWidth = saveHeight = 0;
} }

View File

@@ -3,7 +3,6 @@
Dialog::Dialog(Window& h,std::string text,std::string message, StellarX::MessageBoxType type, bool modal) Dialog::Dialog(Window& h,std::string text,std::string message, StellarX::MessageBoxType type, bool modal)
: Canvas(),message(message), type(type), modal(modal), hWnd(h), titleText(text) : Canvas(),message(message), type(type), modal(modal), hWnd(h), titleText(text)
{ {
this->id = "Dialog";
show = false; show = false;
} }
@@ -34,19 +33,22 @@ void Dialog::draw()
if (dirty && show) if (dirty && show)
{ {
// 保存当前绘图状态 // 保存当前绘图状态
saveStyle(); saveStyle();
// 保存背景(仅在第一次绘制时)
if (saveBkImage == nullptr)
saveBackground((x - BorderWidth), (y - BorderWidth), (width + 2 * BorderWidth), (height + 2 * BorderWidth));
Canvas::setBorderColor(this->borderColor); Canvas::setBorderColor(this->borderColor);
Canvas::setLinewidth(BorderWidth); Canvas::setLinewidth(this->BorderWidth);
Canvas::setCanvasBkColor(this->backgroundColor); Canvas::setCanvasBkColor(this->backgroundColor);
Canvas::setShape(StellarX::ControlShape::ROUND_RECTANGLE); 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) for(auto& c :this->controls)
c->setDirty(true);*/ c->setDirty(true);
restBackground();
Canvas::draw(); Canvas::draw();
//绘制消息文本 //绘制消息文本
@@ -86,12 +88,12 @@ bool Dialog::handleEvent(const ExMessage& msg)
} }
return false; return false;
} }
// 如果正在清理或标记为待清理,则不处理事件 // 如果正在清理或标记为待清理,则不处理事件
if (pendingCleanup || isCleaning) if (pendingCleanup || isCleaning)
return false; return false;
// 模态对话框:点击对话框外部区域时,发出提示音(\a)并吞噬该事件,不允许操作背景内容。 // 模态对话框:点击对话框外部区域时,发出提示音(\a)并吞噬该事件,不允许操作背景内容。
if (modal && msg.message == WM_LBUTTONUP && if (modal && msg.message == WM_LBUTTONUP &&
(msg.x < x || msg.x > x + width || msg.y < y || msg.y > y + height)) (msg.x < x || msg.x > x + width || msg.y < y || msg.y > y + height))
{ {
std::cout << "\a" << std::endl; std::cout << "\a" << std::endl;
@@ -168,11 +170,10 @@ void Dialog::Show()
shouldClose = false; shouldClose = false;
if (modal) if (modal)
{ {
// 模态对话框需要阻塞当前线程直到对话框关闭 // 模态对话框需要阻塞当前线程直到对话框关闭
while (show && !close) while (show && !close)
{ {
// 处理消息 // 处理消息
ExMessage msg; ExMessage msg;
if (peekmessage(&msg, EX_MOUSE | EX_KEY)) if (peekmessage(&msg, EX_MOUSE | EX_KEY))
@@ -190,13 +191,14 @@ void Dialog::Show()
// 重绘 // 重绘
if (dirty) if (dirty)
{ {
requestRepaint(parent); draw();
FlushBatchDraw(); FlushBatchDraw();
} }
// 避免CPU占用过高 // 避免CPU占用过高
Sleep(10); Sleep(10);
} }
// 模态对话框关闭后执行清理 // 模态对话框关闭后执行清理
if (pendingCleanup && !isCleaning) if (pendingCleanup && !isCleaning)
performDelayedCleanup(); performDelayedCleanup();
@@ -216,7 +218,9 @@ void Dialog::Close()
close = true; close = true;
dirty = true; dirty = true;
pendingCleanup = true; // 只标记需要清理,不立即执行 pendingCleanup = true; // 只标记需要清理,不立即执行
auto& c = hWnd.getControls();
for(auto& control:c)
control->setDirty(true);
// 工厂模式下非模态触发回调 返回结果 // 工厂模式下非模态触发回调 返回结果
if (resultCallback&& !modal) if (resultCallback&& !modal)
@@ -457,7 +461,6 @@ void Dialog::initCloseButton()
StellarX::ControlShape::B_RECTANGLE StellarX::ControlShape::B_RECTANGLE
); );
but.get()->setButtonFalseColor(this->backgroundColor); but.get()->setButtonFalseColor(this->backgroundColor);
but.get()->enableTooltip(false);
but->setOnClickListener([this]() { but->setOnClickListener([this]() {
this->SetResult(StellarX::MessageBoxResult::Cancel); this->SetResult(StellarX::MessageBoxResult::Cancel);
this->hWnd.dialogClose = true; this->hWnd.dialogClose = true;
@@ -583,31 +586,6 @@ void Dialog::initDialogSize()
initCloseButton(); // 初始化关闭按钮 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);
}
// 延迟清理策略:由于对话框绘制时保存了背景快照,必须在对话框隐藏后、 // 延迟清理策略:由于对话框绘制时保存了背景快照,必须在对话框隐藏后、
// 所有控件析构前恢复背景,否则会导致背景图像被错误覆盖。 // 所有控件析构前恢复背景,否则会导致背景图像被错误覆盖。
@@ -618,22 +596,20 @@ void Dialog::performDelayedCleanup()
isCleaning = true; isCleaning = true;
auto& c = hWnd.getControls(); // 清除所有控件
for (auto& control : c)
control->setDirty(true);
controls.clear(); controls.clear();
// 重置指针 // 重置指针
closeButton = nullptr; closeButton = nullptr;
title.reset(); title.reset();
// 释放背景图像资源 // 释放背景图像资源
if (saveBkImage && hasSnap) if (saveBkImage && hasSnap)
{ {
restBackground(); restBackground();
FlushBatchDraw();
discardBackground(); discardBackground();
} }
// 重置状态 // 重置状态
needsInitialization = true; needsInitialization = true;
pendingCleanup = false; pendingCleanup = false;
@@ -679,18 +655,4 @@ std::unique_ptr<Button> Dialog::createDialogButton(int x, int y, const std::stri
return btn; return btn;
} }
void Dialog::requestRepaint(Control* parent)
{
if (this == parent)
{
for (auto& control : controls)
if (control->isDirty()&&control->IsVisible())
{
control->draw();
break;
}
}
else
onRequestRepaintAsRoot();
}

View File

@@ -1,381 +0,0 @@
#include "TabControl.h"
inline void TabControl::initTabBar()
{
if (controls.empty())return;
int butW = max(this->width / (int)controls.size(), BUTMINWIDTH);
int butH = max(this->height / (int)controls.size(), BUTMINHEIGHT);
if (this->tabPlacement == StellarX::TabPlacement::Top || this->tabPlacement == StellarX::TabPlacement::Bottom)
for (auto& c : controls)
{
c.first->setHeight(tabBarHeight);
c.first->setWidth(butW);
}
else if (this->tabPlacement == StellarX::TabPlacement::Left || this->tabPlacement == StellarX::TabPlacement::Right)
for (auto& c : controls)
{
c.first->setHeight(butH);
c.first->setWidth(tabBarHeight);
}
int i = 0;
switch (this->tabPlacement)
{
case StellarX::TabPlacement::Top:
for (auto& c : controls)
{
c.first->setX(this->x + i * butW);
c.first->setY(this->y);
i++;
}
break;
case StellarX::TabPlacement::Bottom:
for (auto& c : controls)
{
c.first->setX(this->x + i * butW);
c.first->setY(this->y+this->height - tabBarHeight);
i++;
}
break;
case StellarX::TabPlacement::Left:
for (auto& c : controls)
{
c.first->setX(this->x);
c.first->setY(this->y+i* butH);
i++;
}
break;
case StellarX::TabPlacement::Right:
for (auto& c : controls)
{
c.first->setX(this->x+this->width - tabBarHeight);
c.first->setY(this->y + i * butH);
i++;
}
break;
default:
break;
}
}
inline void TabControl::initTabPage()
{
if (controls.empty())return;
//子控件坐标原点
int nX = 0;
int nY = 0;
switch (this->tabPlacement)
{
case StellarX::TabPlacement::Top:
for (auto& c : controls)
{
c.second->setX(this->x);
c.second->setY(this->y + tabBarHeight);
c.second->setWidth(this->width);
c.second->setHeight(this->height - tabBarHeight);
}
nX = this->x;
nY = this->y + tabBarHeight;
for (auto& c : controls)
{
for (auto& v : c.second->getControls())
{
v->setX(v->getLocalX() + nX);
v->setY(v->getLocalY() + nY);
}
}
break;
case StellarX::TabPlacement::Bottom:
for (auto& c : controls)
{
c.second->setX(this->x);
c.second->setY(this->y);
c.second->setWidth(this->width);
c.second->setHeight(this->height - tabBarHeight);
}
nX = this->x;
nY = this->y;
for (auto& c : controls)
{
for (auto& v : c.second->getControls())
{
v->setX(v->getLocalX() + nX);
v->setY(v->getLocalY() + nY);
}
}
break;
case StellarX::TabPlacement::Left:
for (auto& c : controls)
{
c.second->setX(this->x + tabBarHeight);
c.second->setY(this->y);
c.second->setWidth(this->width - tabBarHeight);
c.second->setHeight(this->height);
}
nX = this->x + tabBarHeight;
nY = this->y;
for (auto& c : controls)
{
for (auto& v : c.second->getControls())
{
v->setX(v->getLocalX() + nX);
v->setY(v->getLocalY() + nY);
}
}
break;
case StellarX::TabPlacement::Right:
for (auto& c : controls)
{
c.second->setX(this->x);
c.second->setY(this->y);
c.second->setWidth(this->width - tabBarHeight);
c.second->setHeight(this->height);
}
nX = this->x;
nY = this->y;
for (auto& c : controls)
{
for (auto& v : c.second->getControls())
{
v->setX(v->getLocalX() + nX);
v->setY(v->getLocalY() + nY);
}
}
break;
default:
break;
}
}
TabControl::TabControl():Canvas()
{
this->id = "TabControl";
}
TabControl::TabControl(int x, int y, int width, int height)
: Canvas(x, y, width, height)
{
this->id = "TabControl";
}
TabControl::~TabControl()
{
}
void TabControl::draw()
{
if (!dirty || !show)return;
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
saveBackground(this->x, this->y, this->width, this->height);
// 恢复背景(清除旧内容)
restBackground();
Canvas::draw();
for (auto& c : controls)
{
c.first->setDirty(true);
c.first->draw();
}
for (auto& c : controls)
if(c.second->IsVisible())
{
c.second->setDirty(true);
c.second->draw();
}
dirty = false;
}
bool TabControl::handleEvent(const ExMessage& msg)
{
if (!show)return false;
bool consume = false;
for (auto& c : controls)
if (c.first->handleEvent(msg))
{
consume = true;
break;
}
for (auto& c : controls)
if(c.second->IsVisible())
if (c.second->handleEvent(msg))
{
consume = true;
break;
}
if (dirty)
requestRepaint(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();
for (auto& c : controls)
{
c.first->onWindowResize();
c.second->onWindowResize();
}
}
int TabControl::getActiveIndex() const
{
int idx = -1;
for (auto& c : controls)
{
idx++;
if (c.first->isClicked())
return idx;
}
return idx;
}
void TabControl::setActiveIndex(int idx)
{
if (idx < 0 || idx > controls.size() - 1) return;
if (controls[idx].first->getButtonMode() == StellarX::ButtonMode::DISABLED)return;
if (controls[idx].first->isClicked())
{
if (controls[idx].second->IsVisible())
return;
else
controls[idx].second->setIsVisible(true);
}
else
{
controls[idx].first->setButtonClick(true);
}
}
int TabControl::count() const
{
return (int)controls.size();
}
int TabControl::indexOf(const std::string& tabText) const
{
int idx = -1;
for(auto& c : controls)
{
idx++;
if (c.first->getButtonText() == tabText)
return idx;
}
return idx;
}
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();
break;
}
else if (control.second->isDirty()&&control.second->IsVisible())
{
control.second->draw();
break;
}
}
}
else
onRequestRepaintAsRoot();
}

View File

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

View File

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

View File

@@ -5,7 +5,6 @@
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()
@@ -28,10 +27,7 @@ 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)
{ {
@@ -91,7 +87,7 @@ bool TextBox::handleEvent(const ExMessage& msg)
flushmessage(EX_MOUSE | EX_KEY); flushmessage(EX_MOUSE | EX_KEY);
} }
if (dirty) if (dirty)
requestRepaint(parent); draw();
if (click) if (click)
click = false; click = false;

View File

@@ -38,8 +38,8 @@ Window::~Window()
void Window::draw() { void Window::draw() {
// 使用 EasyX 创建基本窗口 // 使用 EasyX 创建基本窗口
if (!hWnd) hWnd = initgraph(width, height, windowMode);
hWnd = initgraph(width, height, windowMode); SetWindowText(hWnd, headline.c_str());
// **启用窗口拉伸支持**:添加厚边框和最大化按钮样式 // **启用窗口拉伸支持**:添加厚边框和最大化按钮样式
LONG style = GetWindowLong(hWnd, GWL_STYLE); LONG style = GetWindowLong(hWnd, GWL_STYLE);
style |= WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX; // 可调整边框,启用最大化/最小化按钮 style |= WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX; // 可调整边框,启用最大化/最小化按钮
@@ -65,18 +65,10 @@ void Window::draw(std::string imagePath)
// 使用指定图片绘制窗口背景(铺满窗口) // 使用指定图片绘制窗口背景(铺满窗口)
this->background = new IMAGE(width, height); this->background = new IMAGE(width, height);
bkImageFile = imagePath; bkImageFile = imagePath;
if (!hWnd) hWnd = initgraph(width, height, windowMode);
hWnd = initgraph(width, height, windowMode);
SetWindowText(hWnd, headline.c_str()); SetWindowText(hWnd, headline.c_str());
loadimage(background, imagePath.c_str(), width, height, true); loadimage(background, imagePath.c_str(), width, height, true);
if(background) putimage(0, 0, background);
putimage(0, 0, background);
else
{
// 设置背景色并清屏
setbkcolor(wBkcolor);
cleardevice();
}
// 同样应用可拉伸样式 // 同样应用可拉伸样式
LONG style = GetWindowLong(hWnd, GWL_STYLE); LONG style = GetWindowLong(hWnd, GWL_STYLE);
style |= WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX; style |= WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX;
@@ -85,11 +77,7 @@ void Window::draw(std::string imagePath)
// 绘制控件(含对话框)到窗口 // 绘制控件(含对话框)到窗口
BeginBatchDraw(); BeginBatchDraw();
for (auto& control : controls) for (auto& control : controls) control->draw();
{
control->setDirty(true);
control->draw();
}
for (auto& dlg : dialogs) dlg->draw(); for (auto& dlg : dialogs) dlg->draw();
EndBatchDraw(); EndBatchDraw();
} }
@@ -212,7 +200,8 @@ int Window::runEventLoop()
// 标记所有控件/对话框为脏,确保都补一次背景/外观 // 标记所有控件/对话框为脏,确保都补一次背景/外观
for (auto& c : controls) for (auto& c : controls)
{ {
c->onWindowResize(); c->setDirty(true);
c->updateBackground();
c->draw(); c->draw();
} }
for (auto& d : dialogs) for (auto& d : dialogs)
@@ -220,7 +209,7 @@ int Window::runEventLoop()
auto dd = dynamic_cast<Dialog*>(d.get()); auto dd = dynamic_cast<Dialog*>(d.get());
dd->setDirty(true); dd->setDirty(true);
dd->setInitialization(true); dd->setInitialization(true);
d->draw(); dd->draw();
} }
needResizeDirty = false; needResizeDirty = false;
} }
@@ -339,11 +328,6 @@ IMAGE* Window::getBkImage() const
return this->background; return this->background;
} }
std::string Window::getBkImageFile() const
{
return this->bkImageFile;
}
std::vector<std::unique_ptr<Control>>& Window::getControls() std::vector<std::unique_ptr<Control>>& Window::getControls()
{ {
return this->controls; return this->controls;