完成 StellarX 核心重构、缺陷修复与测试收口

This commit is contained in:
Codex
2026-09-30 01:23:18 +08:00
parent 9155a86a8a
commit 568ffc40eb
85 changed files with 5011 additions and 645 deletions
+211
View File
@@ -0,0 +1,211 @@
/*******************************************************************************
* @类: Button
* @摘要: 多功能按钮控件,支持多种状态和样式
* @描述:
* 提供完整的按钮功能,包括普通点击、切换模式、禁用状态。
* 支持多种形状(矩形、圆形、椭圆等)和丰富的视觉样式。
* 通过回调函数机制实现灵活的交互逻辑。
*
* @特性:
* - 支持三种工作模式:普通、切换、禁用
* - 八种几何形状,各有边框和无边框版本
* - 自定义颜色(默认、悬停、点击状态)
* - 多种填充模式(纯色、图案、图像)
* - 完整的鼠标事件处理(点击、悬停、移出)
*
* @使用场景: 作为主要交互控件,用于触发动作或表示状态
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
******************************************************************************/
#pragma once
#include "Control.h"
#include"label.h"
#define DISABLEDCOLOUR RGB(96, 96, 96) //禁用状态颜色
#define TEXTMARGINS_X 6
#define TEXTMARGINS_Y 4
constexpr int bordWith = 1; //边框宽度,用于快照恢复时的偏移计算
constexpr int bordHeight = 1; //边框高度,用于快照恢复时的偏移计算
class Button : public Control
{
std::string text; // 按钮上的文字
bool click; // 是否被点击
bool hover; // 是否被悬停
std::string cutText; // 切割后的文本
bool needCutText = true; // 是否需要切割文本
bool isUseCutText = false; // 是否使用切割文本
int padX = TEXTMARGINS_X; // 文本最小左右内边距
int padY = TEXTMARGINS_Y; // 文本最小上下内边距
COLORREF buttonTrueColor; // 按钮被点击后的颜色
COLORREF buttonFalseColor; // 按钮未被点击的颜色
COLORREF buttonHoverColor; // 按钮被鼠标悬停的颜色
COLORREF buttonBorderColor = RGB(0, 0, 0);// 按钮边框颜色
StellarX::ButtonMode mode; // 按钮模式
StellarX::ControlShape shape; // 按钮形状
StellarX::FillMode buttonFillMode = StellarX::FillMode::Solid; //按钮填充模式
StellarX::FillStyle buttonFillIma = StellarX::FillStyle::BDiagonal; //按钮填充图案
std::unique_ptr<IMAGE> buttonFileIMAGE; //按钮填充图像
std::function<void()> onClickCallback; //回调函数
std::function<void()> onToggleOnCallback; //TOGGLE模式下的回调函数
std::function<void()> onToggleOffCallback; //TOGGLE模式下的回调函数
public:
// 必须先构造公开字体样式,再用它初始化旧样式快照。
// C++ 按成员声明顺序构造;若顺序反过来,oldStyle 会读取尚未构造的 std::string。
StellarX::ControlText textStyle; // 按钮文字样式
private:
StellarX::ControlText oldStyle = textStyle; // 按钮文字样式
int oldtext_width = -1;
int oldtext_height = -1;
int text_width = 0;
int text_height = 0;
// === Tooltip ===
bool tipEnabled = false; // 是否启用
bool tipVisible = false; // 当前是否显示
bool tipFollowCursor = false; // 是否跟随鼠标
bool tipUserOverride = false; // 是否用户自定义了tip文本
int tipDelayMs = 1000; // 延时(毫秒)
int tipOffsetX = 12; // 相对鼠标偏移
int tipOffsetY = 18;
ULONGLONG tipHoverTick = 0; // 开始悬停的时间戳
int lastMouseX = 0; // 最新鼠标位置(用于定位)
int lastMouseY = 0;
std::string tipTextClick; // NORMAL 模式下用
std::string tipTextOn; // click==true 时用
std::string tipTextOff; // click==false 时用
Label tipLabel; // 直接复用Label作为提示
public:
// 创建使用默认颜色的按钮。
Button(int x, int y, int width, int height, const std::string text,
StellarX::ButtonMode mode = StellarX::ButtonMode::NORMAL, StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
// 创建自定义默认态和点击态颜色的按钮。
Button(int x, int y, int width, int height, const std::string text,
COLORREF ct, COLORREF cf, StellarX::ButtonMode mode = StellarX::ButtonMode::NORMAL,
StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
// 创建自定义默认态、点击态和悬停态颜色的按钮。
Button(int x, int y, int width, int height, const std::string text,
COLORREF ct, COLORREF cf, COLORREF ch,
StellarX::ButtonMode mode = StellarX::ButtonMode::NORMAL, StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
// 析构按钮并释放图像填充资源。
~Button();
// 绘制按钮本体、文本和必要时的 Tooltip。
void draw() override;
// 处理按钮点击、悬停、切换状态和 Tooltip 触发。
bool handleEvent(const ExMessage& msg) override;
// 由窗口时钟推进 Tooltip 到期状态,即使没有新的鼠标消息也能触发显示。
void tick(ULONGLONG nowMs) override;
// 清理按钮的鼠标瞬时状态;用于父容器在 WM_MOUSEMOVE 短路后补做 hover/tooltip 收口。
bool clearTransientMouseState() override;
// Tooltip 可见时,按钮实际写像素范围不再等于按钮本体,需要把 Tooltip 矩形并入 coverage。
// coverage 必须覆盖按钮本体和可见 Tooltip,才能正确恢复旧区域。
RECT getManagedRepaintCoverageRect() const override;
// Button 的持久绘制范围只包含本体;Tooltip 是临时浮层,不能污染兄弟控件背景快照。
RECT getManagedRepaintPersistentCoverageRect() const override;
// 设置 NORMAL 模式下的点击回调
void setOnClickListener(std::function<void()> callback);
// 设置 TOGGLE 模式下切换到打开状态时的回调
void setOnToggleOnListener(std::function<void()> callback);
// 设置 TOGGLE 模式下切换到关闭状态时的回调
void setOnToggleOffListener(std::function<void()> callback);
// 设置按钮模式(NORMAL / TOGGLE / DISABLED)
void setbuttonMode(StellarX::ButtonMode mode);
// 设置圆角矩形的圆角宽度
void setROUND_RECTANGLEwidth(int width);
// 设置圆角矩形的圆角高度
void setROUND_RECTANGLEheight(int height);
// 设置按钮填充模式
void setFillMode(StellarX::FillMode mode);
// 设置按钮图案填充样式
void setFillIma(StellarX::FillStyle ima);
// 设置按钮图像填充资源
void setFillIma(std::string imaName);
// 设置按钮边框颜色
void setButtonBorder(COLORREF Border);
// 设置按钮默认态颜色
void setButtonFalseColor(COLORREF color);
// 设置按钮文本(char* 重载)。
// 会重新计算文本像素尺寸并让自动 Tooltip 文案同步为新文本;传入指针内容会被拷贝。
void setButtonText(const char* text);
// 设置按钮文本(std::string 重载)。
// 若用户未显式覆盖 Tooltip 文案,默认 Tooltip 也会跟随按钮文本更新。
void setButtonText(std::string text);
// 设置按钮几何形状。
void setButtonShape(StellarX::ControlShape shape);
// 直接设置按钮点击状态。
// NORMAL 设置为 true 时触发 onClick;TOGGLE 仅在状态实际变化时触发 on/off 回调,重复设置同值不会重跑回调。
// 回调在 mutation scope 内执行,可以安全排队修改宿主控件树。
void setButtonClick(BOOL click);
// 查询按钮当前是否处于点击/选中状态。
bool isClicked() const;
// 获取按钮文本。
std::string getButtonText() const;
// 获取按钮文本的 C 字符串视图;指针仅在下一次修改按钮文本前有效,不应长期保存。
const char* getButtonText_c() const;
// 获取按钮模式。
StellarX::ButtonMode getButtonMode() const;
// 获取按钮形状。
StellarX::ControlShape getButtonShape() const;
// 获取按钮填充模式。
StellarX::FillMode getFillMode() const;
// 获取按钮图案填充样式。
StellarX::FillStyle getFillIma() const;
// 获取按钮图像填充资源;未设置图像时返回 nullptr。
IMAGE* getFillImaImage() const;
// 获取按钮边框颜色。
COLORREF getButtonBorder() const;
// 获取按钮文字颜色。
COLORREF getButtonTextColor() const;
// 获取按钮文字样式。
StellarX::ControlText getButtonTextStyle() const;
public:
// === Tooltip API===
// 开关 Tooltip;关闭时只修改内部启用/显示状态,不负责智能选位。
// Tooltip 是按钮内部临时浮层,会并入托管重绘 coverage,但不会成为持久背景快照的一部分。
void enableTooltip(bool on);
// 查询 Tooltip 当前是否已经显示。
bool isTooltipVisible() const { return tipVisible; }
// 设置 Tooltip 延时(毫秒);负数会被夹紧为 0。
void setTooltipDelay(int ms) { tipDelayMs = (ms < 0 ? 0 : ms); }
// 设置 Tooltip 是否跟随鼠标
void setTooltipFollowCursor(bool on) { tipFollowCursor = on; }
// 设置 Tooltip 偏移量;当前仅在 setTooltipFollowCursor(true) 时参与定位。
void setTooltipOffset(int dx, int dy) { tipOffsetX = dx; tipOffsetY = dy; }
// 设置 Tooltip 的文字、背景和透明样式。
void setTooltipStyle(COLORREF text, COLORREF bk, bool transparent);
// 设置 NORMAL 模式下 Tooltip 文本,并阻止自动跟随按钮文本。
void setTooltipText(const std::string& s) { tipTextClick = s; tipUserOverride = true; }
// 设置 TOGGLE 模式开/关两种状态下的 Tooltip 文本。
void setTooltipTextsForToggle(const std::string& onText, const std::string& offText);
private:
// 初始化按钮内部状态、颜色和 Tooltip 默认样式
void initButton(const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape, COLORREF ct, COLORREF cf, COLORREF ch);
// 判断鼠标是否在圆形按钮内
bool isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius);
// 判断鼠标是否在椭圆按钮内
bool isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, int height);
// Button 不是模态对话框,固定返回 false。
bool model() const override { return false; }
// 按当前按钮宽度和语言特征裁剪文本
void cutButtonText();
// 统一隐藏 Tooltip 并恢复其背景快照。
void hideTooltip();
// 在最后一次记录的鼠标位置显示 Tooltip。
void showTooltipAtLastMouse();
// 根据当前 click 状态选择 Tooltip 文案
void refreshTooltipTextForState();
};
+134
View File
@@ -0,0 +1,134 @@
/*******************************************************************************
* @类: Canvas
* @摘要: 画布容器控件,用于分组和管理子控件
* @描述:
* 作为其他控件的父容器,提供统一的背景和边框样式。
* 负责将事件传递给子控件并管理它们的绘制顺序。
* 在托管重绘模式下,Canvas 通常作为一组子控件的安全重绘 root。
*
* @特性:
* - 支持四种矩形形状(普通、圆角,各有边框和无边框版本)
* - 可自定义填充模式、边框颜色和背景颜色
* - 自动管理子控件的生命周期和事件传递
* - 支持嵌套容器结构
*
* @使用场景: 用于分组相关控件、实现复杂布局或作为对话框基础
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
*******************************************************************************/
#pragma once
#include "Control.h"
#include"Table.h"
class Canvas : public Control
{
friend class TabControl;
protected:
std::vector<std::unique_ptr<Control>> controls;
// 回调或遍历期间的增删清空先记录在队列中,防止 vector 变更使当前迭代器失效。
enum class ControlMutationKind { Add, Remove, Clear };
struct PendingControlMutation
{
ControlMutationKind kind = ControlMutationKind::Clear;
StellarX::ControlHandle handle{};
std::unique_ptr<Control> control;
};
std::vector<PendingControlMutation> pendingControlMutations;
int mutationScopeDepth = 0;
// 应用队列时禁止递归提交,避免回调再次加入变更造成重入。
bool applyingControlMutations = false;
StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE; //容器形状
StellarX::FillMode canvasFillMode = StellarX::FillMode::Solid; //容器填充模式
StellarX::LineStyle canvasLineStyle = StellarX::LineStyle::Solid; //线型
int canvaslinewidth = 1; //线宽
COLORREF canvasBorderClor = RGB(0, 0, 0); //边框颜色
COLORREF canvasBkClor = RGB(255, 255, 255); //背景颜色
public:
// 默认构造:创建一个基础矩形容器
Canvas();
// 指定初始位置和尺寸构造容器
Canvas(int x, int y, int width, int height);
// 析构 Canvas;子控件由 unique_ptr 自动释放。
~Canvas() {}
// 修改容器运行态 X,并同步重算全部受管理子控件的世界坐标
void setX(int x)override;
// 修改容器运行态 Y,并同步重算全部受管理子控件的世界坐标
void setY(int y)override;
// 修改容器运行态宽度,并同步重算全部受管理子控件的世界坐标
void setWidth(int width) override;
// 修改容器运行态高度,并同步重算全部受管理子控件的世界坐标
void setHeight(int height) override;
// 绘制容器背景、边框和全部可见子控件。
void draw() override;
// 将鼠标和键盘事件按绘制层级分发给子控件。
bool handleEvent(const ExMessage& msg) override;
// 推进容器内可见子控件的时钟状态。
void tick(ULONGLONG nowMs) override;
// 递归清理子树中的 hover / tooltip / 临时按下态,不把 Canvas 自己升级为整块重绘源。
bool clearTransientMouseState() override;
// 添加一个由 Canvas 托管的子控件并返回稳定句柄。
// 若当前处于事件/绘制/tick 事务,unique_ptr 会先进入待提交队列,避免正在遍历的 controls 失效。
StellarX::ControlHandle addControl(std::unique_ptr<Control> control);
// 按句柄移除子控件;事务期间延迟删除。
// 返回 true 表示目标命中且删除请求已排队/提交;返回 false 表示句柄无效或未命中。
bool removeControl(StellarX::ControlHandle handle);
// 清空全部子控件;事务期间延迟到最外层安全点提交。
void clearControls();
// 查找当前已提交子控件;不会返回仍在 pending 队列中的对象。
Control* findControl(StellarX::ControlHandle handle);
// 查找当前已提交子控件的只读视图;不会返回仍在 pending 队列中的对象。
const Control* findControl(StellarX::ControlHandle handle) const;
// 返回当前已提交子控件句柄快照;句柄只表示身份,不保证未来仍有效。
std::vector<StellarX::ControlHandle> getControlHandles() const;
// 设置容器几何形状。
void setShape(StellarX::ControlShape shape);
// 设置容器填充模式。
void setCanvasfillMode(StellarX::FillMode mode);
// 设置容器边框颜色。
void setBorderColor(COLORREF color);
// 设置容器背景填充颜色。
void setCanvasBkColor(COLORREF color);
// 设置容器边框线型。
void setCanvasLineStyle(StellarX::LineStyle style);
// 设置容器边框线宽。
void setLinewidth(int width);
// 设置容器可见性,并向子控件递归同步可见状态。
void setIsVisible(bool visible) override;
// 设置容器脏标记,并按需递归标记子控件。
void setDirty(bool dirty) override;
// 窗口尺寸变化时作废容器和子树快照。
void onWindowResize() override;
// 托管模式下登记为 root;非托管模式下走局部或根级重绘。
// 子控件视觉变化会提升到 Canvas root,保证背景快照和兄弟覆盖关系一致。
void requestRepaint(Control* parent)override;
// 判断 Canvas 或其受管理子树是否存在待提交的脏内容。
bool hasManagedDirtySubtree() const override;
// 获取 Canvas 托管重绘覆盖范围,包含需要随 root 一起重绘的子树区域。
RECT getManagedRepaintCoverageRect() const override;
// 获取 Canvas 持久绘制范围,用于判断兄弟背景快照是否需要失效。
RECT getManagedRepaintPersistentCoverageRect() const override;
// 判断当前 Canvas 是否可安全做局部提交
bool canCommitManagedPartialRepaint() const override;
// 托管收口阶段执行 Canvas 的真正重绘
void commitManagedRepaint() override;
protected:
// 进入 Canvas 子树 mutation transaction;会向父容器/Window 传播事务深度。
void beginMutationScope() override;
// 离开 Canvas 子树 mutation transaction;最外层结束时提交子控件队列。
void endMutationScope() override;
// 提交 add/remove/clear 队列,并递归提交已存在子容器的 pending mutation。
void flushPendingMutations() override;
// 统一解算后,按当前运行态矩形把所有受管理子控件重新映射到新的世界坐标。
void applyResolvedLayoutRect(const StellarX::ResolvedLayoutRect& rect) override;
private:
// 容器自己的几何变化后,需要统一刷新所有子控件的运行态矩形。
void relayoutManagedChildren();
// Canvas 不是模态对话框,固定返回 false。
bool model() const override { return false; };
};
+304
View File
@@ -0,0 +1,304 @@
/*******************************************************************************
* @类: Control
* @摘要: 所有控件的抽象基类,定义通用接口和基础功能
* @描述:
* 提供控件的基本属性和方法,包括位置、尺寸、重绘标记等。
* 实现绘图状态保存和恢复机制,确保控件绘制不影响全局状态。
* 同时提供“事件阶段登记、收口阶段统一提交”的托管重绘基础接口。
*
* @特性:
* - 定义控件基本属性(坐标、尺寸、脏标记)
* - 提供绘图状态管理(saveStyle/restoreStyle)
* - 声明纯虚接口(draw、handleEvent等)
* - 禁止移动语义,禁止拷贝语义
*
* @使用场景: 作为所有具体控件类的基类,不直接实例化
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
******************************************************************************/
#pragma once
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0600
#endif
#ifndef WINVER
#define WINVER _WIN32_WINNT
#endif
#include <windows.h>
#include <vector>
#include <memory>
#include <easyx.h>
#undef MessageBox
#include <iostream>
#include <string>
#include <functional>
#include <atomic>
#include "CoreTypes.h"
class Window;
class Control
{
friend class Window;
friend class Canvas;
protected:
inline static std::atomic<std::uint64_t> nextHandleValue{ 1 };
// 句柄随控件构造生成,容器只通过句柄排队删除,避免遍历期间直接持有失效指针。
StellarX::ControlHandle handle{ nextHandleValue.fetch_add(1, std::memory_order_relaxed) };
std::string id; // 控件ID
int localx, x, localy, y; // local* 为设计态父局部坐标,x/y 为运行态世界坐标
int localWidth, width, localHeight, height; // local* 为设计态尺寸,width/height 为运行态尺寸
Control* parent = nullptr; // 父控件
Window* hostWindow = nullptr; // 宿主窗口(顶层由 Window 注入,子控件可沿 parent 回溯)
bool dirty = true; // 是否重绘
bool show = true; // 是否显示
bool eventVisualChanged = false; // 最近一次 handleEvent 是否真的引发了视觉变化(用于上层判断是否需要登记重绘)
/* == 布局模式 == */
StellarX::LayoutMode layoutMode = StellarX::LayoutMode::Fixed; // 布局模式
StellarX::Anchor anchor_1 = StellarX::Anchor::Top; // 旧版兼容锚点 1
StellarX::Anchor anchor_2 = StellarX::Anchor::Right; // 旧版兼容锚点 2
// 新布局模型:内部统一使用“水平轴 + 垂直轴”的规格描述,不再直接依赖旧锚点求解。
StellarX::LayoutSpec layoutSpec{
{ false, true, StellarX::AxisSizePolicy::FixedSize, StellarX::AxisAlignPolicy::End },
{ true, false, StellarX::AxisSizePolicy::FixedSize, StellarX::AxisAlignPolicy::Start }
};
// 控件能力边界:用于限制某些控件在特定轴上是否允许 Stretch。
StellarX::LayoutCapability layoutCapability{};
/* == 背景快照 == */
std::unique_ptr<IMAGE> saveBkImage;
int saveBkX = 0, saveBkY = 0; // 快照保存起始坐标
int saveWidth = 0, saveHeight = 0; // 快照保存尺寸
bool hasSnap = false; // 当前是否持有有效快照
StellarX::RouRectangle rouRectangleSize; // 圆角矩形椭圆宽度和高度
LOGFONT currentFont{}; // 保存当前字体样式和颜色
COLORREF currentColor{};
COLORREF currentBkColor{}; // 保存当前填充色
COLORREF currentBorderColor{}; // 边框颜色
LINESTYLE currentLineStyle{}; // 保存当前线型
// 禁止拷贝构造,避免两个对象拥有同一个控件身份和背景快照。
Control(const Control&) = delete;
// 禁止拷贝赋值,保持控件所有权和句柄唯一性。
Control& operator=(const Control&) = delete;
// 禁止移动构造,避免容器外部移动后破坏父子关系和快照状态。
Control(Control&&) = delete;
// 禁止移动赋值,控件迁移统一通过 unique_ptr 所有权转移完成。
Control& operator=(Control&&) = delete;
// 构造默认控件几何,供派生类无参构造使用。
Control() : localx(0), x(0), localy(0), y(0), localWidth(100), width(100), height(100), localHeight(100) {}
// 构造指定设计态和运行态矩形的基础控件。
Control(int x, int y, int width, int height)
: localx(x), x(x), localy(y), y(y), localWidth(width), width(width), height(height), localHeight(height) {
}
public:
// 控件内部使用的 RAII 事务守卫。
// 进入后子类可安全排队修改子树;析构时自动结束事务并在最外层触发提交。
class MutationScope
{
// RAII 事务保证异常路径或提前 return 时也能成对结束嵌套变更范围。
Control* owner = nullptr;
public:
// 进入 owner 的 mutation scope;owner 为空时为空操作。
explicit MutationScope(Control* control) : owner(control) { if (owner) owner->beginMutationScope(); }
// 禁止拷贝事务守卫,避免重复结束同一事务。
MutationScope(const MutationScope&) = delete;
// 禁止拷贝赋值事务守卫,事务所有权只能移动。
MutationScope& operator=(const MutationScope&) = delete;
// 移动事务守卫所有权,确保只有一个对象负责 endMutationScope。
MutationScope(MutationScope&& other) noexcept : owner(other.owner) { other.owner = nullptr; }
// 自动离开 owner 的 mutation scope。
~MutationScope() { if (owner) owner->endMutationScope(); }
};
// 析构控件并释放可能存在的背景快照。
virtual ~Control()
{
discardBackground();
}
protected:
// 向上请求重绘:普通路径交给父容器,托管路径则登记到 Window。
// 子类在事件中改变可见像素后应调用它,而不是直接假设自己能立即 draw。
virtual void requestRepaint(Control* parent);
// 根控件/无父时触发重绘;顶层控件通常通过 Window 托管收口统一处理。
virtual void onRequestRepaintAsRoot();
// 当前是否处于 Window 托管分发阶段;若为真,则不应立即画。
// 该判断用于避免事件遍历中边改状态边回贴背景导致闪烁或快照错乱。
bool shouldDeferManagedRepaint() const;
protected:
// 保存指定区域的背景快照,供局部重绘前后恢复使用。
virtual void saveBackground(int x, int y, int w, int h);
// 将已保存的背景快照回贴到屏幕。
virtual void restBackground();
// 回贴旧背景并释放快照
void discardBackground();
public:
// 仅作废快照,不回贴旧背景
void invalidateBackgroundSnapshot();
//“纯作废快照 + 标脏”,不再在 resize 路径里回贴旧背景
virtual void onWindowResize();
// 获取当前运行态 X 坐标。
int getX() const { return x; }
// 获取当前运行态 Y 坐标。
int getY() const { return y; }
// 获取当前运行态宽度。
int getWidth() const { return width; }
// 获取当前运行态高度。
int getHeight() const { return height; }
// 获取当前运行态右边界坐标。
int getRight() const { return x + width; }
// 获取当前运行态下边界坐标。
int getBottom() const { return y + height; }
// 获取设计态父局部 X 坐标。
int getLocalX() const { return localx; }
// 获取设计态父局部 Y 坐标。
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; }
// 公开几何 setter:
// - 面向用户使用
// - 默认只修改运行态矩形
// - 不允许自动回写设计基线 local*
// 若某个复合控件需要在 setter 后维护自己拥有的运行态子树一致性,
// 由该控件在 override 中自行处理,但仍不得越权承担统一布局器职责。
// 修改运行态 X 坐标,不自动回写设计态 localx。
virtual void setX(int x);
// 修改运行态 Y 坐标,不自动回写设计态 localy。
virtual void setY(int y);
// 修改运行态宽度,不自动回写设计态 localWidth。
virtual void setWidth(int width);
// 修改运行态高度,不自动回写设计态 localHeight。
virtual void setHeight(int height);
public:
// 绘制入口。实现应只在 dirty/show 条件满足时写屏,并在完成后清理 dirty。
virtual void draw() = 0;
// 事件入口。返回 true 表示事件已消费;若事件造成视觉变化,应设置 eventVisualChanged 并请求重绘。
virtual bool handleEvent(const ExMessage& msg) = 0;
// 轻量清理接口:仅用于“没有机会收到本次 WM_MOUSEMOVE”的兄弟分支,
// 清掉 hover / tooltip / 临时按下态等鼠标瞬时状态。
// 该接口不是事件分发入口,不参与新的命中判断,也不允许回写设计基线。
virtual bool clearTransientMouseState();
// 由 Window 驱动的单调时钟更新;动画、延时提示等不得依赖额外鼠标消息。
virtual void tick(ULONGLONG) {}
// 设置控件是否显示;隐藏时派生类可同步清理快照和子控件状态。
virtual void setIsVisible(bool show);
// 设置父容器指针;通常由容器接管子控件时调用。
void setParent(Control* newParent) { parent = newParent; }
//设置宿主窗口(通常仅由顶层 Window/对话框注入)
virtual void setHostWindow(Window* host) { this->hostWindow = host; }
Window* getHostWindow() const; // 获取宿主 Window;子控件可沿 parent 向上回溯
RECT getBoundsRect() const; // 获取当前控件外接矩形,用于覆盖/相交判断
// 获取本控件“本轮实际可能写到屏幕上的覆盖范围”。
// 默认等于控件本体矩形;像 Button 这类会额外绘制 Tooltip 的控件,可 override 后扩大范围。
// 托管重绘 coverage、overlay 相交判断统一走这个接口,而不再默认使用控件本体 bounds。
virtual RECT getManagedRepaintCoverageRect() const;
// 获取会影响后续控件背景快照的“持久绘制范围”。
// Tooltip 等临时浮层不应进入该范围,避免上层兄弟补画时把临时浮层抓进背景快照。
virtual RECT getManagedRepaintPersistentCoverageRect() const;
// 找到本控件对应的托管重绘 root;通常是直接挂在 Window 下的控件或 Dialog。
Control* getManagedRepaintRoot();
// 找到“root 下面承接本次脏变化的直接子分支”,用于局部提交时缩小重绘范围。
Control* getManagedRepaintDirectBranch(Control* root);
bool hasValidBackgroundSnapshot() const { return hasSnap && saveBkImage != nullptr; } // 当前是否持有可用于局部恢复的快照
// 当前控件或其受管理子树中是否存在待提交的脏内容;容器需要递归检查子树。
virtual bool hasManagedDirtySubtree() const;
// 当前 root 是否可安全做“局部提交”而非整 root 重画;复杂容器可返回 false 升级为整 root 重绘。
virtual bool canCommitManagedPartialRepaint() const;
// 托管收口阶段真正执行绘制的入口;只应由 Window 的 flushManagedRepaint 调用。
virtual void commitManagedRepaint();
// 设置控件脏标记;true 表示下一轮需要重新绘制。
virtual void setDirty(bool isDirty) { dirty = isDirty; }
// 检查控件当前是否可见。
bool IsVisible() const { return show; };
// 获取控件类型/调试标识。
std::string getId() const { return id; }
// 返回稳定句柄而非对象地址;调用方不得据此推断控件当前仍在容器中。
StellarX::ControlHandle getHandle() const noexcept { return handle; }
// 检查控件当前是否处于待重绘状态。
bool isDirty() { return dirty; }
//获取控件最近一次事件处理是否引发了视觉变化
bool didEventAffectVisual() const { return eventVisualChanged; }
// 判断控件是否为模态对话框;普通控件应返回 false。
virtual bool model()const = 0;
//布局
// 设置旧版布局模式(兼容入口)
void setLayoutMode(StellarX::LayoutMode layoutMode_);
// 设置旧版双锚点输入,并映射到内部统一 LayoutSpec
void setAnchor(StellarX::Anchor anchor_1, StellarX::Anchor anchor_2);
// 直接设置水平轴布局规格。
// 调用该接口后会自动切换到 AnchorToEdges 布局模式;
// 这是新布局模型的公开入口,后设置者会覆盖旧 setAnchor() 对水平轴的映射结果。
void setHorizontalLayoutSpec(const StellarX::AxisLayoutSpec& spec);
// 直接设置垂直轴布局规格。
// 调用该接口后会自动切换到 AnchorToEdges 布局模式;
// 这是新布局模型的公开入口,后设置者会覆盖旧 setAnchor() 对垂直轴的映射结果。
void setVerticalLayoutSpec(const StellarX::AxisLayoutSpec& spec);
// 设置水平轴锚定边集合(left / right)。
void setHorizontalAnchors(bool left, bool right);
// 设置垂直轴锚定边集合(top / bottom)。
void setVerticalAnchors(bool top, bool bottom);
// 设置水平轴尺寸策略(Stretch / FixedSize)。
void setHorizontalSizePolicy(StellarX::AxisSizePolicy policy);
// 设置垂直轴尺寸策略(Stretch / FixedSize)。
void setVerticalSizePolicy(StellarX::AxisSizePolicy policy);
// 设置水平轴固定尺寸位移策略(Start / End / Center / Proportional)。
void setHorizontalAlignPolicy(StellarX::AxisAlignPolicy policy);
// 设置垂直轴固定尺寸位移策略(Start / End / Center / Proportional)。
void setVerticalAlignPolicy(StellarX::AxisAlignPolicy policy);
// 获取旧版锚点 1(兼容读取入口)
StellarX::Anchor getAnchor_1() const;
// 获取旧版锚点 2(兼容读取入口)
StellarX::Anchor getAnchor_2() const;
// 获取旧版布局模式
StellarX::LayoutMode getLayoutMode() const;
// 获取水平轴布局规格;返回的是当前生效的新模型状态,不要求可逆回旧 anchor 语义。
StellarX::AxisLayoutSpec getHorizontalLayoutSpec() const;
// 获取垂直轴布局规格;返回的是当前生效的新模型状态,不要求可逆回旧 anchor 语义。
StellarX::AxisLayoutSpec getVerticalLayoutSpec() const;
// 获取内部统一布局规格;供 Window / Canvas 等统一解算入口使用。
const StellarX::LayoutSpec& getLayoutSpec() const;
// 获取控件能力边界;用于判断某个轴是否允许 Stretch。
const StellarX::LayoutCapability& getLayoutCapability() const;
// 显式将当前运行态矩形提交为新的设计基线。
// 这是“运行态 -> 设计态”的受控入口之一:
// - 普通 resize / 重排过程中不得自动调用
// - 公开 setter 也不得隐式调用
// - 仅用于少数需要把当前结果固化为新设计基线的场景
void commitCurrentGeometryAsDesignRect();
protected:
// 容器和窗口在事件、绘制、tick 期间进入事务;具体容器在最外层结束时提交队列。
MutationScope enterMutationScope() { return MutationScope(this); }
// 进入子树结构变更事务。默认向父容器或宿主 Window 传播,使整棵树共享同一提交边界。
virtual void beginMutationScope();
// 离开子树结构变更事务。最外层结束后才允许容器提交 pending mutation。
virtual void endMutationScope();
// 提交子类容器自己的 pending mutation;普通叶子控件保持空实现。
virtual void flushPendingMutations() {}
// 第 1 层:根据父设计尺寸、父当前尺寸和本控件设计矩形,解算出当前运行态局部矩形。
StellarX::ResolvedLayoutRect resolveLayoutRect(int parentDesignW, int parentDesignH,
int parentWorldX, int parentWorldY, int parentCurrentW, int parentCurrentH) const;
// 内部受控路径:把统一解算结果应用到运行态矩形。不要把公开 setter 当成纯赋值接口来复用。
virtual void applyResolvedLayoutRect(const StellarX::ResolvedLayoutRect& rect);
// 最底层运行态赋值入口:仅修改运行态矩形,不回写 local*。
void applyRuntimeRectDirect(int worldX, int worldY, int width, int height);
// 保存当前 EasyX 绘图状态,避免控件绘制污染全局状态。
void saveStyle();
// 恢复 saveStyle() 保存的 EasyX 绘图状态。
void restoreStyle();
// 清空本轮事件视觉变化标记。
void resetEventVisualChanged() { eventVisualChanged = false; }
// 标记本轮事件是否引发视觉变化。
void markEventVisualChanged(bool changed = true) { eventVisualChanged = changed; }
};
+599
View File
@@ -0,0 +1,599 @@
/*******************************************************************************
* @文件: CoreTypes.h
* @摘要: 星垣(StellarX)框架核心类型定义文件
* @描述:
* 集中定义框架中使用的所有枚举类型和结构体,确保类型一致性。
* 这是框架的类型系统基础,所有控件都依赖于此文件中定义的类型。
*
* @作者: 我在人间做废物
* @日期: September 2025
******************************************************************************/
#pragma once
#include "easyx.h"
#include <cstdint>
#include <string>
/**
* @命名空间: StellarX
*
* @详细说明:
* 集中定义框架中使用的所有枚举类型和结构体,确保类型一致性。
* 这是框架的类型系统基础,所有控件都依赖于此文件中定义的类型
*
* @使用示例:
* StellarX::FillStyle::Horizontal - 填充样式
*
* @备注:
* 不用单独包含此头文件,已在StellarX.h中包含,包含唯一对外头文件即可
*/
namespace StellarX
{
/**
* @结构体名称: ControlHandle
* @功能描述: 表示控件在当前进程内的稳定身份句柄
*
* @详细说明:
* 控件由 Window、Canvas、TabControl 等容器持有所有权,外部调用方不应长期保存
* 控件对象地址。ControlHandle 用一个不暴露对象地址的整数值表示控件身份,
* 用于后续 removeControl、findControl、getControlHandles 等安全查询和删除操作。
*
* 句柄值为 0 时表示无效句柄;非 0 值由控件构造时单调分配,只保证当前进程
* 本次运行期间的身份稳定,不保证跨进程、跨运行或序列化后仍然有效。
*
* @成员说明:
* std::uint64_t value = 0; - 句柄原始值,0 表示无效句柄。
*
* @使用示例:
* StellarX::ControlHandle handle = wnd.addControl(std::move(control));
* if (handle) wnd.removeControl(handle);
*
* @备注:
* 控件被移除后,旧句柄仍可安全用于比较或查询,但 findControl 可能返回 nullptr;
* 调用方不得把句柄视为对象生命周期仍然存在的证明。
*/
struct ControlHandle
{
std::uint64_t value = 0;
// 判断句柄是否有效;value 为 0 时返回 false。
explicit operator bool() const noexcept { return value != 0; }
// 比较两个句柄是否指向同一控件身份。
bool operator==(const ControlHandle& other) const noexcept { return value == other.value; }
// 比较两个句柄是否指向不同控件身份。
bool operator!=(const ControlHandle& other) const noexcept { return value != other.value; }
};
/**
* @枚举类名称: FillStyle
* @功能描述: 用来定义控件填充图案的枚举类
*
* @详细说明:
* 根据此枚举类可以自定义控件填充图案
* 可以在控件初始化时设置填充图案
* 根据具体情况选择不同的填充图案
* 默认填充图案为水平线
*
* @取值说明:
* Horizontal - 水平线
* Vertical - 垂直线
* FDiagonal - 反斜线
* BDiagonal - 正斜线
* Cross - 十字
* DiagCross - 网格
*
* @使用示例:
* FillStyle var = FillStyle::Horizontal;
*
* @备注:
* 此枚举类仅支持图案填充模式
*/
enum class FillStyle
{
Horizontal = HS_HORIZONTAL, // 水平线
Vertical = HS_VERTICAL, // 垂直线
FDiagonal = HS_FDIAGONAL, // 反斜线
BDiagonal = HS_BDIAGONAL, // 正斜线
Cross = HS_CROSS, // 十字
DiagCross = HS_DIAGCROSS // 网格
};
/**
* @枚举类名称: FillMode
* @功能描述: 用来定义控件填充模式的枚举类
*
* @详细说明:
* 根据此枚举类可以自定义控件填充模式
* 可以在控件初始化时设置填充模式
* 根据具体情况选择不同的填充模式
* 默认填充模式为固实填充
*
* @取值说明:
* Solid - 固实填充
* Null - 不填充
* Hatched - 图案填充
* Pattern - 自定义图案填充
* DibPattern - 自定义图像填充
*
* @使用示例:
* FillMode var = FillMode::Solid;
*/
enum class FillMode
{
Solid = BS_SOLID, //固实填充
Null = BS_NULL, // 不填充
Hatched = BS_HATCHED, // 图案填充
Pattern = BS_PATTERN, // 自定义图案填充
DibPattern = BS_DIBPATTERN // 自定义图像填充
};
/**
* @枚举类名称: LineStyle
* @功能描述: 此枚举类用来定义控件边框线型
*
* @详细说明:
* 根据此枚举类可以自定义控件边框线型
* 可以在控件初始化时设置边框线型
* 根据具体情况选择不同的线型
* 默认线型为实线
*
* @取值说明:
* Solid // 实线
* Dash // 虚线
* Dot // 点线
* DashDot // 点划线
* DashDotDot // 双点划线
* Null // 无线
*
* @使用示例:
* LineStyle var = LineStyle::Solid;
*/
enum class LineStyle
{
Solid = PS_SOLID, // 实线
Dash = PS_DASH, // 虚线
Dot = PS_DOT, // 点线
DashDot = PS_DASHDOT, // 点划线
DashDotDot = PS_DASHDOTDOT, // 双点划线
Null = PS_NULL // 无线
};
/**
* @结构体名称: ControlText
* @功能描述: 控件字体样式 可以自定义不同的样式
*
* @详细说明:
* 主要使用的场景为:需要修改或想自定义控件字体大小,字体样式,颜色等
*
* @成员说明:
* int nHeight = 0; - 字体高度
* int nWidth = 0; - 字体宽度 如果为0则自适应
* std::string faceName = "微软雅黑"; - 字体名称
* COLORREF color = RGB(0, 0, 0); - 字体颜色
* int nEscapement = 0; - 字符串旋转角度
* int nOrientation = 0; - 字符旋转角度
* int nWeight = 0; - 字体粗细 范围0~1000 0表示默认
* bool bItalic = false; - 是否斜体
* bool bUnderline = false; - 是否下划线
* bool bStrikeOut = false; - 是否删除线
*/
struct ControlText
{
int nHeight = 0; //- 字体高度
int nWidth = 0; //- 字体宽度 如果为0则自适应
// 由对象拥有字体名,避免从临时字符串或外部缓冲区保存悬空指针。
std::string faceName = "微软雅黑"; //- 字体名称
COLORREF color = RGB(0, 0, 0); //- 字体颜色
int nEscapement = 0; //- 字符串旋转角度
int nOrientation = 0; //- 字符旋转角度
int nWeight = 0; //- 字体粗细 范围0~1000 0表示默认
bool bItalic = false; //- 是否斜体
bool bUnderline = false; //- 是否下划线
bool bStrikeOut = false; //- 是否删除线
// 比较两份字体样式是否完全一致。
bool operator==(const ControlText& text) const;
// 比较两份字体样式是否存在任意差异。
bool operator!=(const ControlText& text) const { return !(*this == text); }
};
/**
* @枚举名称: ControlShape
* @功能描述: 枚举控件的不同几何样式
*
* @详细说明:
* 定义了四种(有无边框算一种)不同的几何样式,可以根据具体需求
* 自定义控件的形状。
*
* @取值说明:
* RECTANGLE = 1, //有边框矩形
* B_RECTANGLE, //无边框矩形
* ROUND_RECTANGLE, //有边框圆角矩形
* B_ROUND_RECTANGLE, //无边框圆角矩形
* CIRCLE, //有边框圆形
* B_CIRCLE, //无边框圆形
* ELLIPSE, //有边框椭圆
* B_ELLIPSE //无边框椭圆
*
* @使用示例:
* ControlShape shape = ControlShape::ELLIPSE;
*
* @备注:
* 按钮类支持所有形状,部分控件只支持部分形状,具体请参考控件类。
*/
enum class ControlShape
{
RECTANGLE = 1, //有边框矩形
B_RECTANGLE, //无边框矩形
ROUND_RECTANGLE, //有边框圆角矩形
B_ROUND_RECTANGLE, //无边框圆角矩形
CIRCLE, //有边框圆形
B_CIRCLE, //无边框圆形
ELLIPSE, //有边框椭圆
B_ELLIPSE //无边框椭圆
};
/**
* @枚举类名称: TextBoxmode
* @功能描述: 定义了文本框的三种模式
*
* @详细说明:
* 需要限制文本框是否接受用户输入时使用
*
* @取值说明:
* INPUT_MODE, // 用户可输入模式
* READONLY_MODE // 只读模式
* PASSWORD_MODE // 密码模式
*/
enum class TextBoxmode
{
INPUT_MODE, // 用户可输入模式
READONLY_MODE, // 只读模式
PASSWORD_MODE // 密码模式
};
/**
* @枚举名称: ButtonMode
* @功能描述: 定义按钮的工作模式
*
* @详细说明:
* 根据按钮的工作模式,按钮可以有不同的行为。
* 用户可以在具体情况下设置按钮的工作模式。
*
* @取值说明:
* NORMAL = 1, - 普通模式,点击后触发回调,但不会保持状态。
* TOGGLE, - 切换模式,点击后会在选中和未选中之间切换,触发不同的回调函数。
* DISABLED - 禁用模式,按钮不可点击,显示为灰色,文本显示删除线。
*
* @使用示例:
* ButtonMode mode = ButtonMode::NORMAL;
*/
enum class ButtonMode
{
NORMAL = 1, //普通模式
TOGGLE, //切换模式
DISABLED //禁用模式
};
/**
* @结构体名称: RouRectangle
* @功能描述: 定义了控件圆角矩形样式时圆角的椭圆尺寸
*
* @详细说明:
* 需要修改控件圆角矩形样式时的圆角椭圆。
*
* @成员说明:
* int ROUND_RECTANGLEwidth = 20; //构成圆角矩形的圆角的椭圆的宽度。
* int ROUND_RECTANGLEheight = 20; //构成圆角矩形的圆角的椭圆的高度。
*/
struct RouRectangle
{
int ROUND_RECTANGLEwidth = 20; //构成圆角矩形的圆角的椭圆的宽度。
int ROUND_RECTANGLEheight = 20; //构成圆角矩形的圆角的椭圆的高度。
};
/**
* @枚举名称: MessageBoxType
* @功能描述: 定义标准消息框按钮组合
*
* @详细说明:
* MessageBoxType 决定 Dialog / MessageBox 工厂创建哪些功能按钮,
* 例如只有确定按钮、确定取消按钮、是否按钮等。
*
* @取值说明:
* OK - 只有确定按钮
* OKCancel - 确定和取消按钮
* YesNo - 是和否按钮
* YesNoCancel - 是、否和取消按钮
* RetryCancel - 重试和取消按钮
* AbortRetryIgnore - 中止、重试和忽略按钮
*
* @使用示例:
* StellarX::MessageBoxType type = StellarX::MessageBoxType::YesNo;
*/
enum class MessageBoxType
{
OK, // 只有确定按钮
OKCancel, // 确定和取消按钮
YesNo, // 是和否按钮
YesNoCancel, // 是、否和取消按钮
RetryCancel, // 重试和取消按钮
AbortRetryIgnore, // 中止、重试和忽略按钮
};
/**
* @枚举名称: MessageBoxResult
* @功能描述: 定义消息框关闭后的用户选择结果
*
* @详细说明:
* 模态消息框通过 showModal 返回该枚举;非模态消息框通过 showAsync 的回调返回。
* 当前取值与 Win32 常见 MessageBox 返回码保持一致,便于迁移和日志排查。
*
* @取值说明:
* OK - 用户点击确定
* Cancel - 用户点击取消或关闭对话框
* Yes - 用户点击是
* No - 用户点击否
* Abort - 用户点击中止
* Retry - 用户点击重试
* Ignore - 用户点击忽略
*
* @使用示例:
* StellarX::MessageBoxResult result =
* StellarX::MessageBox::showModal(wnd, "继续执行?", "确认", StellarX::MessageBoxType::YesNo);
*/
enum class MessageBoxResult
{
OK = 1, // 确定按钮
Cancel = 2, // 取消按钮
Yes = 6, // 是按钮
No = 7, // 否按钮
Abort = 3, // 中止按钮
Retry = 4, // 重试按钮
Ignore = 5 // 忽略按钮
};
#if 0 //布局管理器相关 —待实现—
/*
*
*@枚举名称: LayoutKind
* @功能描述 : 定义布局管理类型
*
*@详细说明 :
* 根据布局管理类型,控件可以有不同的布局方式。
* 用户可以在具体情况下设置布局管理类型。
*
* @取值说明 :
* Absolute:不管,保持子控件自己的坐标(向后兼容)。
* HBox: 水平方向排队;支持固定宽、权重分配、对齐、拉伸。
* VBox: 竖直方向排队;同上。
* Grid(网格):按行列摆;支持固定/自适应/权重行列;支持跨行/跨列;单元内对齐/拉伸。
*
*/
// 布局类型
enum class LayoutKind
{
Absolute = 1,
HBox,
VBox,
Grid,
Flow,
Stack
};
// 布局参数
struct LayoutParams
{
// 边距左、右、上、下
int marginL = 0, marginR = 0, marginT = 0, marginB = 0;
// 固定尺寸(>=0 强制;-1 用控件当前尺寸)
int fixedW = -1, fixedH = -1;
// 主轴权重(HBox=宽度、VBox=高度、Grid见下)
float weight = 0.f;
// 对齐(非拉伸时生效)
enum Align { Start = 0, Center = 1, End = 2, Stretch = 3 };
int alignX = Start; // HBox: 次轴=Y;VBox: 次轴=X;Grid: 单元内
int alignY = Start; // Grid :控制单元内垂直(HBox / VBox通常只用 alignX)
// Grid 专用(可先不做)
int gridRow = 0, gridCol = 0, rowSpan = 1, colSpan = 1;
// Flow 专用(可先不做)
int flowBreak = 0; // 1=强制换行
};
#endif
/*
* @枚举名称: TabPlacement
* @功能描述: 定义了选项卡页签的不同位置
*
* @详细说明:
* 根据选项卡页签的位置,选项卡页签可以有不同的布局方式。
*
* @成员说明:
* Top, - 选项卡页签位于顶部
* Bottom, - 选项卡页签位于底部
* Left, - 选项卡页签位于左侧
* Right - 选项卡页签位于右侧
*
* @使用示例:
* TabPlacement placement = TabPlacement::Top;
*/
enum class TabPlacement
{
Top,
Bottom,
Left,
Right
};
/*
* @枚举名称: LayoutMode
* @功能描述: 定义了两种布局模式
*
* @详细说明:
* 根据不同模式,在窗口拉伸时采用不同的布局策略
*
* @成员说明:
* Fixed, - 固定布局
* AnchorToEdges - 锚定布局
*
* @使用示例:
* LayoutMode mode = LayoutMode::Fixed;
*/
enum class LayoutMode
{
Fixed,
AnchorToEdges
};
/*
* @枚举名称: Anchor
* @功能描述: 定义了控件相对于窗口锚定的位置
*
* @详细说明:
* 根据不同的锚定位置,有不同的拉伸策略
*
* @成员说明:
* Top, - 锚定上边,控件上边与窗口上侧距离保持不变
* Bottom, - 锚定底边,控件底边与窗口底边距离保持不变
* Left, - 锚定左边,控件左边与窗口左侧距离保持不变
* Right - 锚定右边,控件上边与窗口右侧距离保持不变
*
* @使用示例:
* Anchor a = Anchor::Top;
*/
enum class Anchor
{
NoAnchor = 0,
Left = 1,
Right,
Top,
Bottom
};
/*******************************************************************************
* @枚举: AxisSizePolicy
* @摘要: 单轴尺寸策略,决定该轴是否允许因父容器变化而拉伸
* @详细说明:
* 该枚举只决定“尺寸是否变化”,不决定“位置如何求解”。
* 若需要在固定尺寸下决定控件如何位移,应结合 AxisAlignPolicy 一起使用。
*
* @成员说明:
* Stretch - 允许尺寸随父容器变化而变化
* FixedSize - 尺寸保持设计态大小,仅位置按锚点/位移策略变化
******************************************************************************/
enum class AxisSizePolicy
{
Stretch,
FixedSize
};
/*******************************************************************************
* @枚举: AxisAlignPolicy
* @摘要: 单轴固定尺寸时的位置策略
* @详细说明:
* 该策略只在“该轴不拉伸”时生效,用于决定控件在父容器变化后如何重新定位。
* 其中 Proportional 只用于位置求解,不参与尺寸缩放。
*
* @成员说明:
* Start - 保持起边距离
* End - 保持终边距离
* Center - 保持相对父容器中心的偏移关系
* Proportional - 保持设计态中的相对位置比例
******************************************************************************/
enum class AxisAlignPolicy
{
Start,
End,
Center,
Proportional
};
/*******************************************************************************
* @结构体名称: AxisLayoutSpec
* @摘要: 描述单个坐标轴上的锚点与解算策略
* @详细说明:
* 一个控件完整布局由“水平轴 + 垂直轴”两份 AxisLayoutSpec 组成。
* anchorStart / anchorEnd 只表达当前轴是否锚定起止边;
* sizePolicy 决定该轴是否允许拉伸;
* alignPolicy 则用于固定尺寸时的位置求解。
*
* @成员说明:
* bool anchorStart - 是否锚定当前轴起边(水平轴为左边,垂直轴为上边)。
* bool anchorEnd - 是否锚定当前轴终边(水平轴为右边,垂直轴为下边)。
* AxisSizePolicy sizePolicy - 当前轴尺寸策略,决定是否随父容器变化而拉伸。
* AxisAlignPolicy alignPolicy - 固定尺寸时的位置策略。
*
* @备注:
* anchorStart / anchorEnd 不直接决定尺寸,尺寸是否变化以 sizePolicy 为准。
******************************************************************************/
struct AxisLayoutSpec
{
bool anchorStart = false;
bool anchorEnd = false;
AxisSizePolicy sizePolicy = AxisSizePolicy::FixedSize;
AxisAlignPolicy alignPolicy = AxisAlignPolicy::Start;
};
/*******************************************************************************
* @结构体名称: LayoutSpec
* @摘要: 控件完整布局规格,由水平轴和垂直轴两部分组成
* @详细说明:
* LayoutSpec 是控件对统一布局器的完整声明。水平轴和垂直轴分别独立描述
* 锚点、尺寸策略和固定尺寸时的位置策略,Window / Canvas 在 resize 时据此
* 解算运行态矩形。
*
* @成员说明:
* AxisLayoutSpec horizontal - 水平轴布局规格。
* AxisLayoutSpec vertical - 垂直轴布局规格。
******************************************************************************/
struct LayoutSpec
{
AxisLayoutSpec horizontal{};
AxisLayoutSpec vertical{};
};
/*******************************************************************************
* @结构体名称: LayoutCapability
* @摘要: 控件能力边界,声明控件在哪些轴上允许 Stretch
* @详细说明:
* 规则表负责定义“某种组合如何解算”,LayoutCapability 负责声明
* “该控件是否允许采用这种组合”。例如 Table 当前阶段仅允许 X 轴拉伸,
* 因此 allowStretchY 会被显式关闭。
*
* @成员说明:
* bool allowStretchX - 是否允许该控件在水平轴使用 Stretch 尺寸策略。
* bool allowStretchY - 是否允许该控件在垂直轴使用 Stretch 尺寸策略。
*
* @备注:
* 能力边界用于保护控件自身实现约束;即使调用方设置了 Stretch,
* 不被允许的轴也应被布局层拒绝或降级处理。
******************************************************************************/
struct LayoutCapability
{
bool allowStretchX = true;
bool allowStretchY = true;
};
/*******************************************************************************
* @结构体名称: ResolvedLayoutRect
* @摘要: 统一布局解算后的运行态矩形结果
* @详细说明:
* localX/localY 表示解算完成后的“父局部坐标”;
* worldX/worldY 表示映射到窗口世界坐标后的最终绘制位置;
* width/height 为运行态尺寸。
*
* @成员说明:
* int localX - 解算后的父局部 X 坐标。
* int localY - 解算后的父局部 Y 坐标。
* int width - 解算后的运行态宽度。
* int height - 解算后的运行态高度。
* int worldX - 映射到窗口坐标系后的 X 坐标。
* int worldY - 映射到窗口坐标系后的 Y 坐标。
******************************************************************************/
struct ResolvedLayoutRect
{
int localX = 0;
int localY = 0;
int width = 0;
int height = 0;
int worldX = 0;
int worldY = 0;
};
}
+141
View File
@@ -0,0 +1,141 @@
/*******************************************************************************
* @类: Dialog
* @摘要: 模态和非模态对话框控件,提供丰富的消息框功能
* @描述:
* 实现完整的对话框功能,支持多种按钮组合和异步结果回调。
* 自动处理布局、背景保存恢复和生命周期管理。
* 在窗口托管重绘模式下,Dialog 自身也是一个独立的重绘 root。
*
* @特性:
* - 支持六种标准消息框类型(OK、YesNo、YesNoCancel等)
* - 模态和非模态两种工作模式
* - 自动文本换行和尺寸计算
* - 背景保存和恢复机制
* - 工厂模式下的去重检测
*
* @使用场景: 用户提示、确认操作、输入请求等交互场景
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
******************************************************************************/
#pragma once
#include"StellarX.h"
#define closeButtonWidth 30 //关闭按钮宽度
#define closeButtonHeight 20 //关闭按钮高度 同时作为对话框标题栏高度
#define functionButtonWidth 70 //按钮宽度
#define functionButtonHeight 30 //按钮高度
#define buttonMargin 10 //按钮间距
#define buttonAreaHeight 50 //按钮区域高度
#define titleToTextMargin 10 //标题到文本的距离
#define textToBorderMargin 10 //文本到边框的距离
#define BorderWidth 3 //边框宽度
class Dialog : public Canvas
{
friend class Window;
Window& hWnd; //窗口引用
int textWidth = 0; //文本宽度
int textHeight = 0; //文本高度
int buttonNum = 0; // 按钮数量
StellarX::MessageBoxType type = StellarX::MessageBoxType::OK; //对话框类型
std::string titleText = "提示"; //标题文本
std::string message; //提示信息
std::vector<std::string> lines; //消息内容按行分割
bool needsInitialization = true; // 是否需要根据当前内容重新初始化布局和内部按钮
bool modal = true; //是否模态
COLORREF backgroundColor = RGB(240, 240, 240); //背景颜色
COLORREF borderColor = RGB(100, 100, 100); //边框颜色
COLORREF buttonTrueColor = RGB(211, 190, 190); //按钮被点击颜色
COLORREF buttonFalseColor = RGB(215, 215, 215); //按钮未被点击颜色
COLORREF buttonHoverColor = RGB(224, 224, 224); //按钮悬浮颜色
Button* closeButton = nullptr; //关闭按钮
StellarX::MessageBoxResult result = StellarX::MessageBoxResult::Cancel; // 对话框结果
bool isCleaning = false; //是否正在清理
bool pendingCleanup = false; //延迟清理
public:
StellarX::ControlText textStyle; // 字体样式
// 在事件安全点执行延迟清理;用于非模态关闭后统一回收。
// Close() 只隐藏并标记待清理,真正释放内部按钮和快照要等到这里执行。
void performDelayedCleanup();
// 非模态工厂调用时用于回传结果;回调可能修改宿主控件树,因此 Close() 会在 mutation scope 内触发它。
std::function<void(StellarX::MessageBoxResult)> resultCallback;
// 设置非模态结果回调;仅在非模态 Dialog 关闭时异步调用。
void SetResultCallback(std::function<void(StellarX::MessageBoxResult)> cb);
// 获取标题文本,用于非模态去重
std::string GetCaption() const;
// 获取正文文本,用于非模态去重
std::string GetText() const;
public:
// 构造对话框;modal=false 时按非模态浮层工作,由 Window 接管生命周期和层级。
Dialog(Window& hWnd, std::string text, std::string message = "对话框", StellarX::MessageBoxType type = StellarX::MessageBoxType::OK, bool modal = true);
// 析构对话框并释放内部控件资源。
~Dialog();
// 绘制对话框背景、标题、正文和功能按钮。
void draw() override;
// 处理关闭按钮和功能按钮事件。
bool handleEvent(const ExMessage& msg) override;
// 设置标题并标记布局需要重建。
void SetTitle(const std::string& title);
// 设置消息内容并标记文本换行和布局需要重建。
void SetMessage(const std::string& message);
// 设置对话框类型并重建功能按钮组合。
void SetType(StellarX::MessageBoxType type);
// 设置模态属性,决定 Show() 使用阻塞循环还是交给 Window 主循环。
void SetModal(bool modal);
// 设置对话框结果,通常由功能按钮回调写入。
void SetResult(StellarX::MessageBoxResult result);
// 获取对话框结果
StellarX::MessageBoxResult GetResult() const;
// 返回当前对话框是否为模态;Window 用它区分阻塞式对话框和托管浮层。
bool model() const override;
// 显示对话框。模态对话框会进入自己的消息循环;非模态对话框只标记可见,由 Window 主循环驱动。
void Show();
// 关闭对话框。
// 该调用只设置隐藏、结果和待清理标志,并请求整场景重绘;对象释放延迟到 performDelayedCleanup()。
void Close();
// 宿主窗口变化时仅重新居中,不拉伸 Dialog 自身
void recenterInHostWindow();
private:
// 根据当前 MessageBoxType 初始化功能按钮。
void initButtons();
// 初始化标题栏关闭按钮。
void initCloseButton();
// 按当前消息内容和宽度约束分割显示行。
void splitMessageLines();
// 计算当前标题和正文所需的文本尺寸。
void getTextSize();
// 标记需要重新布局并按需清空旧控件
void invalidateLayout(bool clearChildren);
// 根据文本和按钮数量初始化对话框尺寸。
void initDialogSize();
// 依据当前 Dialog 的 x/y/width/height 重新创建标题和按钮
void rebuildChrome();
// 禁止走普通 Canvas 添加路径;Dialog 内部控件由自身 chrome 重建逻辑统一维护
void addControl(std::unique_ptr<Control> control);
// 判断当前 Dialog 是否可安全做局部提交。
bool canCommitManagedPartialRepaint() const override;
// 托管收口阶段执行 Dialog 的真正重绘。
void commitManagedRepaint() override;
// 清除 Dialog 内部所有 chrome 控件。
void clearControls();
// 创建标准对话框功能按钮。
std::unique_ptr<Button> createDialogButton(int x, int y, const std::string& text);
// 请求宿主窗口重绘 Dialog root;非托管模式下立即更新内部按钮。
void requestRepaint(Control* parent) override;
};
+71
View File
@@ -0,0 +1,71 @@
/*******************************************************************************
* @类: Label
* @摘要: 简单文本标签控件,用于显示静态文本
* @描述:
* 提供基本的文本显示功能,支持透明背景和自定义样式。
* 不支持用户交互,专注于文本呈现。
*
* @特性:
* - 支持背景透明/不透明模式
* - 完整的文本样式控制(字体、颜色、效果)
* - 自动适应文本内容
* - 轻量级无事件处理开销
*
* @使用场景: 显示说明文字、标题、状态信息等静态内容
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
******************************************************************************/
#pragma once
#include "Control.h"
class Label : public Control
{
std::string text; //标签文本
COLORREF textBkColor; //标签背景颜色
bool textBkDisap = false; //标签背景是否透明
std::string lastMeasuredText; // 最近一次参与尺寸测量的文本
StellarX::ControlText lastMeasuredStyle; // 最近一次参与尺寸测量的字体样式
bool contentMeasureValid = false; // 当前内容尺寸缓存是否有效
// Label 不处理输入事件,固定返回 false。
bool handleEvent(const ExMessage&) override { return false; }
// Label 不是模态对话框,固定返回 false。
bool model() const override { return false; };
// 根据当前文本和字体样式刷新运行态尺寸。
// 这是 Label 的内容驱动几何入口:
// - 覆盖文本变化
// - 覆盖字体样式变化
// - 只更新运行态宽高,不自动提交设计基线 local*
void refreshContentDrivenRuntimeGeometry();
public:
// 标签字体样式保持公开,便于用户直接改字段。
// 但直接修改 textStyle 后,必须显式调用 setDirty(true),
// 才会触发内容驱动尺寸刷新并让新的运行态宽高生效。
StellarX::ControlText textStyle; //标签文本样式
public:
// 默认构造:创建一个内容驱动尺寸的标签
Label();
// 指定位置、文本和基础样式构造标签
Label(int x, int y, std::string text = "标签", COLORREF textcolor = BLACK, COLORREF bkColor = RGB(255, 255, 255));
// 绘制标签文本
void draw() override;
// 隐藏 Label 并恢复背景;主要供 Button Tooltip 关闭时复用。
void hide();
// Label 的内容尺寸由文本和字体样式共同决定。
// 因此只要进入 dirty 状态,就在绘制前先刷新一次运行态尺寸,
// 避免 draw() 阶段继续承担新的几何决策。
void setDirty(bool dirty) override;
// 设置标签背景是否透明
void setTextdisap(bool key);
// 设置标签背景颜色
void setTextBkColor(COLORREF color);
// 设置标签文本,并触发内容驱动尺寸刷新。
// Label 的布局系统只负责位置;运行态宽高仍由文本和 textStyle 测量结果决定。
void setText(std::string text);
protected:
// 统一布局器只负责安置 Label 的运行态位置;
// 宽高仍由 Label 自己的内容驱动路径决定。
void applyResolvedLayoutRect(const StellarX::ResolvedLayoutRect& rect) override;
};
+46
View File
@@ -0,0 +1,46 @@
/*******************************************************************************
* @类: MessageBox
* @摘要: 消息框工厂类,提供简化的对话框创建接口
* @描述:
* 封装对话框的创建和显示逻辑,提供静态方法供快速调用。
* 支持模态阻塞和非模态回调两种使用方式。
*
* @特性:
* - 静态方法调用,无需实例化
* - 自动处理模态和非模态的逻辑差异
* - 集成到窗口的对话框管理系统中
* - 提供去重机制防止重复对话框
*
* @使用场景: 快速创建标准消息框,减少样板代码
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
******************************************************************************/
#pragma once
#include <string>
#include <functional>
#include "CoreTypes.h"
#include "Dialog.h"
#include "Window.h"
namespace StellarX
{
class MessageBox
{
public:
// 模态:直接创建并显示对话框,阻塞到用户关闭后返回结果。
// 调用期间会运行 Dialog 自己的模态事件循环。
static MessageBoxResult showModal(Window& wnd,
const std::string& text,
const std::string& caption = "提示",
MessageBoxType type = MessageBoxType::OK);
// 非模态:创建后立即返回,由 Window 托管生命周期和绘制层级。
// 若同一窗口中已存在相同 caption/text 的可见非模态对话框,则不会重复创建。
// 用户关闭时通过 onResult 异步返回结果;回调允许排队修改宿主控件树。
static void showAsync(Window& wnd,
const std::string& text,
const std::string& caption = "提示",
MessageBoxType type = MessageBoxType::OK,
std::function<void(MessageBoxResult)> onResult = nullptr);
};
} // namespace StellarX
+45
View File
@@ -0,0 +1,45 @@
/*******************************************************************************
* @文件: StellarX.h
* @摘要: 星垣(StellarX) GUI框架 - 主包含头文件
* @版本: v3.1.0
* @描述:
* 一个为Windows平台打造的轻量级、模块化C++ GUI框架。
* 基于EasyX图形库,提供简洁易用的API和丰富的控件。
*
* 通过包含此单一头文件,即可使用框架的所有功能。
* 内部头文件包含顺序由框架维护,用户代码不应依赖该顺序。
*
* @作者: 我在人间做废物
* @邮箱: [3150131407@qq.com] | [ysm3150131407@gmail.com]
* @官网:https://stellarx-gui.top/
* @仓库: https://github.com/Ysm-04/StellarX
* @博客:https://blog.stellarx-gui.top/
*
* @许可证: MIT License
* @版权: Copyright (c) 2025 我在人间做废物
*
* @使用说明:
* 只需包含此文件即可使用框架所有功能。
* 示例: #include "StellarX.h"
* @包含模块:
* CoreTypes.h / SxLog.h / Control.h / Canvas.h / Window.h
* Button.h / Label.h / TextBox.h / Table.h
* Dialog.h / MessageBox.h / TabControl.h
******************************************************************************/
#pragma once
#include "CoreTypes.h"
#include "SxLog.h"
#include "Control.h"
#include"Canvas.h"
#include"Window.h"
#include"Button.h"
#include"Label.h"
#include"TextBox.h"
#include"Table.h"
#include"Dialog.h"
#include"MessageBox.h"
#include"TabControl.h"
+425
View File
@@ -0,0 +1,425 @@
#pragma once
/********************************************************************************
* @文件: SxLog.h
* @摘要: StellarX 日志系统对外接口定义(控制台/文件输出 + 级别过滤 + Tag过滤 + 中英文选择)
* @描述:
* 该日志系统采用“宏 + RAII(析构提交)”的方式实现:
* - 调用端通过 SX_LOGD/SX_LOGI... 写日志
* - 宏内部先 shouldLog 短路过滤,未命中时不构造对象、不拼接字符串
* - 命中时构造 SxLogLine,使用 operator<< 拼接内容
* - 语句结束时 SxLogLine 析构,统一提交到 SxLogger::logLine 输出
*
* 输出通道(Sink)目前提供:
* - ConsoleSink: 写入 std::cout(不走 WinAPI 调试输出通道)
* - FileSink: 写入文件,支持按字节阈值滚动
*
* @特性:
* - 日志级别:Trace/Debug/Info/Warn/Error/Fatal/Off
* - Tag 过滤:None/Whitelist/Blacklist
* - 可选前缀:时间戳/级别/Tag/线程ID/源码位置
* - 中英文选择:SX_T(zh, en) / setLanguage
* - 文件滚动:rotateBytes > 0 时按阈值滚动
*
* @使用场景:
* - 排查重绘链路、脏标记传播、Tab 切换、Table 数据刷新等时序问题
* - 输出可复现日志,配合回归验证
*
* @注意:
* - SX_T 仅做“字符串选择”,不做编码转换
* - 控制台显示是否乱码由“终端 codepage/字体/环境”决定
* - 该头文件只声明接口,实现位于 SxLog.cpp
*
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
********************************************************************************/
// SxLog.h - header-only interface (implementation in SxLog.cpp)
// Pure standard library: std::cout and optional file sink.
#include <atomic>
#include <chrono>
#include <cstdint>
#include <ctime>
#include <cstdio>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <thread>
#include <vector>
#ifndef SX_LOG_ENABLE
#define SX_LOG_ENABLE 1
#endif
namespace StellarX
{
/* ========================= 日志级别 ========================= */
// 说明:
// - minLevel 表示最低输出级别,小于 minLevel 的日志会被 shouldLog 直接过滤
// - Off 表示全局关闭
enum class SxLogLevel : int
{
Trace = 0, // 最细粒度:高频路径追踪(谨慎开启)
Debug = 1, // 调试信息:状态变化/关键分支
Info = 2, // 业务信息:关键流程节点
Warn = 3, // 警告:非致命但异常的情况
Error = 4, // 错误:功能失败、需要关注
Fatal = 5, // 致命:通常意味着无法继续运行
Off = 6 // 关闭全部日志
};
/* ========================= 语言选择 ========================= */
// 说明:仅用于 SX_T 选择输出哪一段文本,不做编码转换
enum class SxLogLanguage : int
{
ZhCN = 0, // 中文
EnUS = 1 // 英文
};
/* ========================= Tag 过滤模式 ========================= */
// None : 不过滤,全部输出
// Whitelist : 只输出 tagList 中包含的 tag
// Blacklist : 输出除 tagList 以外的 tag
enum class SxTagFilterMode : int
{
None = 0,
Whitelist = 1,
Blacklist = 2
};
/* ========================= 日志配置 ========================= */
// 说明:SxLogger 内部持有该配置,shouldLog 与 logLine 都依赖它
struct SxLogConfig
{
SxLogLevel minLevel = SxLogLevel::Info; // 最低输出级别
bool showTimestamp = true; // 是否输出时间戳前缀
bool showLevel = true; // 是否输出级别前缀
bool showTag = true; // 是否输出 tag 前缀
bool showThreadId = false; // 是否输出线程ID(排查并发时开启)
bool showSource = false; // 是否输出源码位置(file:line func)
bool autoFlush = true; // 每行写完是否 flush(排查问题更稳,性能略差)
SxTagFilterMode tagFilterMode = SxTagFilterMode::None; // Tag 过滤模式
std::vector<std::string> tagList; // Tag 列表(白名单/黑名单)
bool fileEnabled = false; // 文件输出是否启用(enableFile 成功才为 true)
std::string filePath; // 文件路径
bool fileAppend = true; // 是否追加写入
std::size_t rotateBytes = 0; // 滚动阈值(0 表示不滚动)
};
/* ========================= Sink 接口 ========================= */
// 说明:
// - Sink 负责“把完整的一行日志写到某个地方”
// - SxLogger 负责过滤/格式化/分发
class ILogSink
{
public:
virtual ~ILogSink() = default;
// 返回 Sink 名称,用于调试识别(例如 "console"/"file")
virtual const char* name() const = 0;
// 写入一整行(调用方保证 line 已包含换行或按约定追加换行)
virtual void writeLine(const std::string& line) = 0;
// 刷新缓冲(可选实现)
virtual void flush() {}
};
/* ========================= 控制台输出 Sink ========================= */
// 作用:把日志写入指定输出流(默认用 std::cout)
class ConsoleSink : public ILogSink
{
public:
// 绑定一个输出流引用(常见用法:std::cout)
explicit ConsoleSink(std::ostream& os) : out(os) {}
// 返回控制台 sink 的固定名称。
const char* name() const override { return "console"; }
// 写入一行(不自动追加换行,换行由上层统一拼接)
void writeLine(const std::string& line) override { out << line; }
// 立即 flush(当 autoFlush=true 时由 SxLogger 调用)
void flush() override { out.flush(); }
private:
std::ostream& out; // 输出流引用(不负责生命周期)
};
/* ========================= 文件输出 Sink ========================= */
// 作用:把日志写入文件,支持按字节阈值滚动
class FileSink : public ILogSink
{
public:
// 构造一个尚未打开文件的文件 sink。
FileSink() = default;
// 返回文件 sink 的固定名称。
const char* name() const override { return "file"; }
// 打开文件
// path : 文件路径
// append : true 追加写;false 清空重写
bool open(const std::string& path, bool append);
// 关闭文件(安全可重复调用)
void close();
// 查询文件是否处于打开状态
bool isOpen() const;
// 设置滚动阈值(字节)
// bytes = 0 表示不滚动
void setRotateBytes(std::size_t bytes) { rotateBytes = bytes; }
// 写入一行,并在需要时触发滚动
void writeLine(const std::string& line) override;
// flush 文件缓冲
void flush() override;
private:
// 检查并执行滚动
// 返回值:是否发生滚动(或是否重新打开)
bool rotateIfNeeded();
std::ofstream ofs; // 文件输出流
std::string filePath; // 当前文件路径
bool appendMode = true; // 是否追加模式(用于 reopen)
std::size_t rotateBytes = 0; // 滚动阈值
// 时间戳精度不足时用递增序号补足文件名唯一性,避免连续滚动互相覆盖。
std::uint64_t rotateSequence = 0;
};
/* ========================= 日志中心 SxLogger ========================= */
// 作用:
// - 保存配置(SxLogConfig)
// - 过滤(level/tag/sink enabled)
// - 格式化前缀(时间/级别/tag/线程/源码位置)
// - 分发到 console/file 等 sink
class SxLogger
{
public:
// 仅用于 Windows 控制台:把 codepage 切到 GBK,解决中文乱码。
// 不使用 WinAPI:内部通过 system("chcp 936") 实现
// 注意:这只影响终端解释输出字节的方式,不影响源码文件编码
static void setGBK();
// 获取全局单例
// 说明:函数内静态对象,C++11 起保证线程安全初始化
static SxLogger& Get();
// 设置最低输出级别
void setMinLevel(SxLogLevel level);
// 获取最低输出级别
SxLogLevel getMinLevel() const;
// 设置语言(用于 SX_T 选择)
void setLanguage(SxLogLanguage lang);
// 获取当前语言
SxLogLanguage getLanguage() const;
// 设置 Tag 过滤
// mode: None/Whitelist/Blacklist
// tags: 过滤列表(精确匹配)
void setTagFilter(SxTagFilterMode mode, const std::vector<std::string>& tags);
// 清空 Tag 过滤(恢复 None)
void clearTagFilter();
// 开关控制台输出
void enableConsole(bool enable);
// 开启文件输出
// path : 文件路径
// append : 追加写/清空写
// rotateBytes: 滚动阈值(0 不滚动)
// 返回值:是否打开成功
bool enableFile(const std::string& path, bool append = true, std::size_t rotateBytes = 0);
// 关闭文件输出(不影响控制台输出)
void disableFile();
// 快速判定是否需要输出(宏层面的短路依赖它)
// 说明:
// - shouldLog 一定要“副作用为 0”
// - 若返回 false,调用端不会创建 SxLogLine,也不会拼接字符串
bool shouldLog(SxLogLevel level, const char* tag) const;
// 输出一条完整日志
// 说明:这是统一出口,SxLogLine 析构最终会走到这里
void logLine(
SxLogLevel level,
const char* tag,
const char* file,
int line,
const char* func,
const std::string& msg);
// 获取配置副本(避免外部直接改内部 cfg)
SxLogConfig getConfigCopy() const;
// 批量设置配置(整体替换);新文件 sink 打开成功后才替换旧配置。
bool setConfig(const SxLogConfig& cfg);
// 工具:把级别转为字符串(用于前缀)
static const char* levelToString(SxLogLevel level);
// 工具:生成本地时间戳字符串(用于前缀与文件滚动名)
static std::string makeTimestampLocal();
private:
// 构造日志单例的初始配置和默认 sink。
SxLogger();
// 判断 tag 是否允许输出(根据 Tag 过滤模式与 tagList)
static bool tagAllowed(const SxLogConfig& cfg, const char* tag);
// 生成前缀(调用方需已持有锁)
std::string formatPrefixUnlocked(
const SxLogConfig& cfg,
SxLogLevel level,
const char* tag,
const char* file,
int line,
const char* func) const;
mutable std::mutex mtx; // 保护 cfg 与 sink 写入,确保多线程行级一致性
SxLogConfig cfg; // 当前配置
std::atomic<SxLogLanguage> lang; // 语言开关(仅影响 SX_T 选择)
std::unique_ptr<ConsoleSink> consoleSink; // 控制台 sink(enableConsole 控制)
std::unique_ptr<FileSink> fileSink; // 文件 sink(enableFile 控制)
};
/* ========================= 双语选择辅助 ========================= */
// 说明:
// - 只做“选择 zhCN 或 enUS”,不做编码转换
// - 输出显示是否正常由终端环境决定
// 根据当前日志语言选择中文或英文文本。
inline const char* SxT(const char* zhCN, const char* enUS)
{
return (SxLogger::Get().getLanguage() == SxLogLanguage::ZhCN) ? zhCN : enUS;
}
#if defined(__cpp_char8_t) && (__cpp_char8_t >= 201811L)
// 说明:
// - C++20 的 u8"xxx" 是 char8_t*,为了兼容调用端,这里提供重载
// - reinterpret_cast 只是改指针类型,不做 UTF-8 -> GBK 转码
// 兼容 C++20 char8_t 字符串字面量的双语选择入口。
inline const char* SxT(const char8_t* zhCN, const char* enUS)
{
return (SxLogger::Get().getLanguage() == SxLogLanguage::ZhCN)
? reinterpret_cast<const char*>(zhCN)
: enUS;
}
#endif
/* ========================= RAII 日志行对象 ========================= */
// 作用:
// - 构造时记录 level/tag/源码位置
// - operator<< 拼接内容
// - 析构时统一提交给 SxLogger::logLine 输出
//
// 设计意义:
// - 避免调用端忘记写换行
// - 保证一行日志作为整体写出
class SxLogLine
{
public:
// 构造:记录元信息(不输出)
SxLogLine(SxLogLevel level, const char* tag, const char* file, int line, const char* func);
// 析构:提交输出(真正写出发生在这里)
~SxLogLine();
// 拼接内容(流式写法)
template<typename T>
// 向当前日志行追加一个可流式输出的值。
SxLogLine& operator<<(const T& v)
{
ss << v;
return *this;
}
private:
SxLogLevel lvl; // 日志级别
const char* tg; // Tag(不拥有内存)
const char* srcFile; // 源文件名(来自 __FILE__)
int srcLine; // 行号(来自 __LINE__)
const char* srcFunc; // 函数名(来自 __func__)
std::ostringstream ss; // 内容拼接缓冲
};
/* ========================= RAII 作用域计时对象 ========================= */
// 作用:
// - 仅在 shouldLog(Trace, tag) 为 true 时启用计时
// - 析构时输出耗时(微秒)
//
// 使用建议:
// - 只在需要定位性能瓶颈时开启 Trace
// - name 建议传入常量字符串,便于检索
class SxLogScope
{
public:
// 构造:根据 shouldLog 决定是否启用计时
SxLogScope(SxLogLevel level, const char* tag, const char* file, int line, const char* func, const char* name);
// 析构:若启用则输出耗时
~SxLogScope();
private:
bool enabled = false; // 是否启用(未启用则析构无输出)
SxLogLevel lvl = SxLogLevel::Trace; // 级别(通常用 Trace)
const char* tg = nullptr; // Tag
const char* srcFile = nullptr; // 源文件
int srcLine = 0; // 行号
const char* srcFunc = nullptr; // 函数
const char* scopeName = nullptr; // 作用域名
std::chrono::steady_clock::time_point t0; // 起始时间点
};
} // namespace StellarX
#if SX_LOG_ENABLE
// SX_T:双语选择宏,调用 SxT 根据当前语言选择输出
#define SX_T(zh, en) ::StellarX::SxT(zh, en)
// 日志宏说明:
// 1) 先 shouldLog 短路过滤,未命中则不会构造 SxLogLine,也不会执行 else 分支的表达式
// 2) 命中则构造临时 SxLogLine,并允许继续使用 operator<< 拼接
// 3) 语句结束时临时对象析构,触发真正输出
#define SX_LOG_TRACE(tag) if(!::StellarX::SxLogger::Get().shouldLog(::StellarX::SxLogLevel::Trace, tag)) ; else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Trace, tag, __FILE__, __LINE__, __func__)
#define SX_LOGD(tag) if(!::StellarX::SxLogger::Get().shouldLog(::StellarX::SxLogLevel::Debug, tag)) ; else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Debug, tag, __FILE__, __LINE__, __func__)
#define SX_LOGI(tag) if(!::StellarX::SxLogger::Get().shouldLog(::StellarX::SxLogLevel::Info, tag)) ; else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Info, tag, __FILE__, __LINE__, __func__)
#define SX_LOGW(tag) if(!::StellarX::SxLogger::Get().shouldLog(::StellarX::SxLogLevel::Warn, tag)) ; else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Warn, tag, __FILE__, __LINE__, __func__)
#define SX_LOGE(tag) if(!::StellarX::SxLogger::Get().shouldLog(::StellarX::SxLogLevel::Error, tag)) ; else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Error, tag, __FILE__, __LINE__, __func__)
#define SX_LOGF(tag) if(!::StellarX::SxLogger::Get().shouldLog(::StellarX::SxLogLevel::Fatal, tag)) ; else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Fatal, tag, __FILE__, __LINE__, __func__)
// 作用域耗时统计宏:默认用 Trace 级别
#define SX_TRACE_SCOPE(tag, nameLiteral) ::StellarX::SxLogScope sx_scope_##__LINE__(::StellarX::SxLogLevel::Trace, tag, __FILE__, __LINE__, __func__, nameLiteral)
#else
// 关闭日志时的兼容宏:保证调用端代码不需要改动
#define SX_T(zh, en) (en)
#define SX_LOG_TRACE(tag) if(true) {} else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Off, tag, "", 0, "")
#define SX_LOGD(tag) if(true) {} else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Off, tag, "", 0, "")
#define SX_LOGI(tag) if(true) {} else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Off, tag, "", 0, "")
#define SX_LOGW(tag) if(true) {} else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Off, tag, "", 0, "")
#define SX_LOGE(tag) if(true) {} else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Off, tag, "", 0, "")
#define SX_LOGF(tag) if(true) {} else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Off, tag, "", 0, "")
#define SX_TRACE_SCOPE(tag, nameLiteral) do {} while(0)
#endif
+118
View File
@@ -0,0 +1,118 @@
/*******************************************************************************
* @类: TabControl
* @摘要: 选项卡容器控件,管理“页签按钮 + 对应页面(Canvas)”
* @描述:
* 提供页签栏布局(上/下/左/右)、选中切换、页内容区域定位;
* 与 Button 一起工作,支持窗口大小变化、可见性联动与脏区重绘。
* 在托管重绘模式下,TabControl 作为“页签栏 + 当前页面”的统一重绘 root。
*
* @特性:
* - 页签栏四向排列(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; //页签栏高度
bool IsFirstDraw = true; //首次绘制标记
int defaultActivation = -1; //默认激活页签索引
StellarX::TabPlacement tabPlacement = StellarX::TabPlacement::Top; //页签排列方式
std::vector<std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>> controls; //页签/页列表
// addTab 在事务期间暂存完整的按钮/页面对,提交时保持两者的索引一致。
std::vector<std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>> pendingTabs;
private:
using Canvas::addControl; // 禁止外部误用
void addControl(std::unique_ptr<Control>) = delete; // 精准禁用该重载
private:
// 初始化页签按钮位置和尺寸
inline void initTabBar();
// 初始化页面 Canvas 的位置和尺寸
inline void initTabPage();
// 统一刷新 TabControl 当前运行态下的页签栏和页面区布局。
void refreshRuntimeLayout();
// 立即加入一组已校验的页签按钮和页面 Canvas,并建立内部回调联动。
size_t addTabDirect(std::unique_ptr<Button> button, std::unique_ptr<Canvas> page);
public:
// 默认构造:创建一个空 TabControl
TabControl();
// 指定初始位置和尺寸构造 TabControl
TabControl(int x, int y, int width, int height);
// 析构 TabControl;页签按钮和页面由 unique_ptr 自动释放。
~TabControl();
// 修改 TabControl 运行态 X,并刷新页签栏和页面区布局。
void setX(int x)override;
// 修改 TabControl 运行态 Y,并刷新页签栏和页面区布局。
void setY(int y)override;
// 修改 TabControl 运行态宽度,并刷新页签栏和页面区布局。
void setWidth(int width) override;
// 修改 TabControl 运行态高度,并刷新页签栏和页面区布局。
void setHeight(int height) override;
// 绘制页签栏和当前可见页面。
void draw() override;
// 处理页签按钮和当前页面的事件分发。
bool handleEvent(const ExMessage& msg) override;
// 只推进当前页按钮和页面中的时钟状态,避免不可见页签产生无效工作。
void tick(ULONGLONG nowMs) override;
// 只转发清理页签按钮和当前可见页面中的鼠标临时状态,不升级为整块 TabControl 重绘。
bool clearTransientMouseState() override;
// 添加一个“页签按钮 + 页面 Canvas”组合并返回页签索引。
// button/page 为空时返回 size_t(-1)。事务期间返回包含 pendingTabs 的逻辑索引,
// 因此调用方可以立即把控件追加到刚创建但尚未提交的页面中。
size_t addTab(std::unique_ptr<Button> button, std::unique_ptr<Canvas> page);
// 向指定页追加控件。成功时返回空指针;失败时把控件所有权原样返还调用方。
// pageIndex 可指向已提交页面,也可指向 addTab 在同一事务中返回的 pending 逻辑索引。
std::unique_ptr<Control> tryAddToPage(size_t pageIndex, std::unique_ptr<Control> control);
// 按页签文本追加控件;若存在重复标题,只匹配第一个命中的页签。
std::unique_ptr<Control> tryAddToPage(const std::string& tabText, std::unique_ptr<Control> control);
// 设置页签位置,并重新计算页签栏和页面区布局。
void setTabPlacement(StellarX::TabPlacement placement);
// 设置页签栏高度;当页签在左右两侧时表示页签栏宽度。
void setTabBarHeight(int height);
// 设置 TabControl 可见性,并同步页签按钮和页面可见状态。
void setIsVisible(bool visible) override;
// 窗口尺寸变化时作废页签按钮和页面快照。
void onWindowResize() override;
// 获取当前激活页签索引;没有激活页时返回 -1。
int getActiveIndex() const;
// 设置当前激活页签索引。
// 首次绘制前仅记录默认激活项;绘制后索引必须命中已提交页签。重复激活当前页会直接返回 true。
bool setActiveIndex(int idx);
// 获取页签数量
int count() const;
// 通过页签文本查找索引;重复标题时返回第一个命中项,未命中返回 -1。
int indexOf(const std::string& tabText) const;
// 设置 TabControl 及其当前页签/页面的脏状态。
void setDirty(bool dirty) override;
// 请求父控件重绘;托管模式下登记 TabControl 为 root。
void requestRepaint(Control* parent)override; // 托管模式下登记为 root;非托管模式下局部更新脏按钮/脏页面
// 判断页签栏或当前页面中是否存在待提交的脏内容。
bool hasManagedDirtySubtree() const override;
// 获取页签栏和当前页面的托管重绘覆盖范围。
RECT getManagedRepaintCoverageRect() const override;
// 获取页签栏和当前页面的持久绘制覆盖范围。
RECT getManagedRepaintPersistentCoverageRect() const override;
bool canCommitManagedPartialRepaint() const override; // 判断当前 TabControl 是否可安全做局部提交
void commitManagedRepaint() override; // 托管收口阶段执行 TabControl 的真正重绘
protected:
// 将回调期间加入的页签和页内控件在外层事务结束时一次性提交。
void flushPendingMutations() override;
// 外层统一解算后,TabControl 需要同步刷新其内部页签栏和页面区。
void applyResolvedLayoutRect(const StellarX::ResolvedLayoutRect& rect) override;
};
+189
View File
@@ -0,0 +1,189 @@
/*******************************************************************************
* @类: Table
* @摘要: 高级表格控件,支持分页显示和大数据量展示
* @描述:
* 提供完整的数据表格功能,包括表头、数据行、分页导航等。
* 自动计算列宽行高,支持自定义样式和交互。
*
* @特性:
* - 自动分页和页码计算
* - 可配置的每页行数
* - 自定义边框样式和填充模式
* - 翻页按钮和页码显示
* - 背景缓存优化渲染性能
*
* @使用场景: 数据展示、报表生成、记录浏览等表格需求
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
******************************************************************************/
#pragma once
#include "Control.h"
#include "Button.h"
#include "Label.h"
// === Table metrics (layout) ===
#define TABLE_PAD_X 10 // 单元格左右内边距
#define TABLE_PAD_Y 5 // 单元格上下内边距
#define TABLE_COL_GAP 20 // 列间距(列与列之间)
#define TABLE_HEADER_EXTRA 10 // 表头额外高度(若不想复用 pad 计算)
#define TABLE_ROW_EXTRA 10 // 行额外高度(同上;或直接用 2*TABLE_PAD_Y)
#define TABLE_BTN_GAP 12 // 页码与按钮的水平间距
#define TABLE_BTN_PAD_H 12 // 按钮水平 padding
#define TABLE_BTN_PAD_V 0 // 按钮垂直 padding(initButton)
#define TABLE_BTN_TEXT_PAD_V 8 // 计算页脚高度时的按钮文字垂直 padding(initTextWaH)
#define TABLE_FOOTER_PAD 16 // 页脚额外高度(底部留白)
#define TABLE_FOOTER_BLANK 8 // 页脚顶部留白
#define TABLE_PAGE_TEXT_OFFSET_X (-40) // 页码文本的临时水平修正
// === Table defaults (theme) ===
#define TABLE_DEFAULT_ROWS_PER_PAGE 5
#define TABLE_DEFAULT_BORDER_WIDTH 1
#define TABLE_DEFAULT_BORDER_COLOR RGB(0,0,0)
#define TABLE_DEFAULT_BG_COLOR RGB(255,255,255)
// === Strings (i18n ready) ===
#define TABLE_STR_PREV "上一页"
#define TABLE_STR_NEXT "下一页"
#define TABLE_STR_PAGE_PREFIX "第"
#define TABLE_STR_PAGE_MID "页/共"
#define TABLE_STR_PAGE_SUFFIX "页"
class Table :public Control
{
private:
std::vector<std::vector<std::string>> data; // 表格数据
std::vector<std::string> headers; // 表格表头
std::string pageNumtext = "页码标签"; // 页码标签文本
int tableBorderWidth = TABLE_DEFAULT_BORDER_WIDTH; // 边框宽度
std::vector<int> colWidths; // 每列的宽度
std::vector<int> lineHeights; // 每行的高度
int rowsPerPage = TABLE_DEFAULT_ROWS_PER_PAGE; // 每页显示的行数
int currentPage = 1; // 当前页码
int totalPages = 1; // 总页数
bool isShowPageButton = true; // 是否显示翻页按钮
bool isNeedDrawHeaders = true; // 是否需要绘制表头(暂时废弃,单做保留,后期优化可能用到)
bool isNeedCellSize = true; // 是否需要计算单元格尺寸
bool isNeedButtonAndPageNum = true; // 是否需要计算翻页按钮和页码信息
std::unique_ptr<Button> prevButton; // 上一页按钮
std::unique_ptr<Button> nextButton; // 下一页按钮
std::unique_ptr<Label> pageNum; //页码文本
int dX = x, dY = y; // 单元格的开始坐标
int uX = x, uY = y; // 单元格的结束坐标
int pX = 0; //标签左上角坐标
int pY = 0; //标签左上角坐标
StellarX::FillMode tableFillMode = StellarX::FillMode::Solid; //填充模式
StellarX::LineStyle tableLineStyle = StellarX::LineStyle::Solid; // 线型
COLORREF tableBorderClor = TABLE_DEFAULT_BORDER_COLOR; // 表格边框颜色
COLORREF tableBkClor = TABLE_DEFAULT_BG_COLOR; // 表格背景颜色
void initTextWaH(); // 初始化文本像素宽度和高度,并据此更新列宽/行高缓存
void initButton(); // 初始化或刷新上一页/下一页按钮
void initPageNum(); // 初始化或刷新页码标签
void drawTable(); // 绘制当前页的数据行
void drawHeader(); // 绘制表头区域
void drawPageNum(); // 绘制页码信息
void drawButton(); // 绘制翻页按钮
// 统一解算后的内部应用入口:
// Table 需要复用自己已有的 setWidth/setX 等副作用逻辑,因此单独接管应用过程。
void applyResolvedLayoutRect(const StellarX::ResolvedLayoutRect& rect) override;
private:
// Table 不是模态对话框,固定返回 false。
bool model() const override { return false; };
public:
StellarX::ControlText textStyle; // 文本样式
// 修改表格运行态 X,并同步内部页脚元素位置
void setX(int x) override;
// 修改表格运行态 Y,并同步内部页脚元素位置
void setY(int y) override;
// 修改表格运行态宽度;当前支持横向拉伸。
// 会重新计算列宽、页脚控件位置和托管重绘 coverage。
void setWidth(int width) override;
// 修改表格运行态高度;当前版本作为行为边界明确忽略纵向 Stretch。
// 表格高度由表头、行高、页脚和按钮布局共同计算,外部写入会被有意丢弃。
void setHeight(int height) override;
public:
// 构造一个基础表格,后续通过表头和数据初始化内容
Table(int x, int y);
// 析构表格并释放内部按钮、页码标签等资源。
~Table();
// 绘制表格背景、表头、当前页数据、页码和翻页按钮。
void draw() override;
// 处理翻页按钮事件,并在页码变化时请求重绘。
bool handleEvent(const ExMessage& msg) override;
// 只清理分页按钮的 hover / tooltip / 临时按下态,不触发表格整体重绘。
bool clearTransientMouseState() override;
// Table 重绘时会一并绘制页码 Label 和分页按钮,coverage 需要把这些内部绘制单元并入。
RECT getManagedRepaintCoverageRect() const override;
// 持久 coverage 排除分页按钮 Tooltip 等临时浮层。
RECT getManagedRepaintPersistentCoverageRect() const override;
// 设置表头;会按新的表头列数规范化已有数据行,并重新计算分页和列宽。
void setHeaders(std::initializer_list<std::string> headers);
// 设置表格数据(单行追加)。
// 该接口是 append 语义,不会替换已有数据;如需覆盖请先 clearData() 或 resetTable()。
void setData(std::vector<std::string> data);
// 设置表格数据(多行追加);如需覆盖请先 clearData() 或 resetTable()
void setData(std::initializer_list<std::vector<std::string>> data);
// 设置每页显示的行数;小于 1 会被夹紧为 1,并同步修正当前页和总页数。
void setRowsPerPage(int rows);
// 设置是否显示翻页按钮。
void showPageButton(bool isShow);
// 设置表格边框颜色。
void setTableBorder(COLORREF color);
// 设置表格背景颜色。
void setTableBk(COLORREF color);
// 设置表格填充模式。
void setTableFillMode(StellarX::FillMode mode);
// 设置表格边框线型。
void setTableLineStyle(StellarX::LineStyle style);
// 设置表格边框宽度。
void setTableBorderWidth(int width);
// 清空表头并标记尺寸缓存失效。
void clearHeaders();
// 清空表格数据并重置分页状态。
void clearData();
// 清空表头和数据,恢复为空表状态。
void resetTable();
// 窗口变化时丢弃背景快照并标记表格需要重绘。
void onWindowResize() override;
//************************** 获取属性 *****************************/
// 获取当前页码。
int getCurrentPage() const;
// 获取总页数。
int getTotalPages() const;
// 获取每页显示的行数。
int getRowsPerPage() const;
// 获取是否显示翻页按钮。
bool getShowPageButton() const;
// 获取表格边框颜色。
COLORREF getTableBorder() const;
// 获取表格背景颜色。
COLORREF getTableBk() const;
// 获取表格填充模式。
StellarX::FillMode getTableFillMode() const;
// 获取表格边框线型。
StellarX::LineStyle getTableLineStyle() const;
// 获取表头副本。
std::vector<std::string> getHeaders() const;
// 获取表格数据副本。
std::vector<std::vector<std::string>> getData() const;
// 获取表格边框宽度。
int getTableBorderWidth() const;
// 获取表格当前运行态尺寸;高度为内部内容计算结果,不受 setHeight() 外部写入驱动。
int getTableWidth() const;
// 获取表格当前运行态高度。
int getTableHeight() const;
};
+62
View File
@@ -0,0 +1,62 @@
/*******************************************************************************
* @类: TextBox
* @摘要: 文本框控件,支持输入和只读两种模式
* @描述:
* 提供文本输入和显示功能,集成EasyX的InputBox用于数据输入。
* 支持有限的形状样式和视觉定制。
*
* @特性:
* - 两种工作模式:输入模式和只读模式
* - 最大字符长度限制
* - 集成系统输入框简化文本输入
* - 支持四种矩形形状变体
*
* @使用场景: 数据输入、文本显示、表单字段等
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
******************************************************************************/
#pragma once
#include "Control.h"
class TextBox : public Control
{
std::string text; //文本
StellarX::TextBoxmode mode; //模式
StellarX::ControlShape shape; //形状
bool click = false; //是否点击
size_t maxCharLen = 10;//最大字符长度
COLORREF textBoxBkClor = RGB(255, 255, 255); //背景颜色
COLORREF textBoxBorderClor = RGB(0, 0, 0); //边框颜色
public:
StellarX::ControlText textStyle; //文本样式
// 构造文本框;支持输入模式和只读模式
TextBox(int x, int y, int width, int height, std::string text = "", StellarX::TextBoxmode mode = StellarX::TextBoxmode::INPUT_MODE, StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
// 绘制文本框
void draw() override;
// 处理输入框鼠标交互和文本录入
bool handleEvent(const ExMessage& msg) override;
// 设置文本框模式。
// PASSWORD_MODE 只改变显示形态:内部仍保存真实文本,绘制时临时生成掩码。
void setMode(StellarX::TextBoxmode mode);
// 设置可输入最大长度;当前按 std::string 字节长度限制,不等同于 Unicode 字符数。
// len 为 0 时保持原值;截断逻辑会尽量落在完整 MBCS/GBK 字符边界上。
void setMaxCharLen(size_t len);
// 设置文本框形状;仅支持矩形/圆角矩形,圆形和椭圆会回退为 RECTANGLE。
void setTextBoxshape(StellarX::ControlShape shape);
// 设置文本框边框颜色。
void setTextBoxBorder(COLORREF color);
// 设置文本框背景颜色。
void setTextBoxBk(COLORREF color);
// 设置当前文本。
// 超过 maxCharLen 时按字节长度截断;已绘制且存在父控件时会请求父级托管重绘。
void setText(std::string text);
// 获取当前真实文本;PASSWORD_MODE 下也返回未掩码内容。
std::string getText() const;
private:
// TextBox 不是模态对话框,固定返回 false。
bool model() const override { return false; };
};
+191
View File
@@ -0,0 +1,191 @@
/**
* Window(头文件)
*
* 设计目标:
* - 提供一个基于 Win32 + EasyX 的“可拉伸且稳定不抖”的窗口容器。
* - 通过消息过程子类化(WndProcThunk)接管关键消息(WM_SIZING/WM_SIZE/...)。
* - 将“几何变化记录(pendingW/H)”与“统一收口重绘(needResizeDirty)”解耦。
* - 在事件分发阶段只改状态并登记重绘 root,在事件尾统一提交,减少分离绘制造成的闪烁。
*
* 关键点(与 .cpp 中实现对应):
* - isSizing:处于交互拉伸阶段时,冻结重绘;松手后统一重绘,防止抖动。
* - WM_SIZING:只做“最小尺寸夹紧”,不回滚矩形、不做对齐;把其余交给系统。
* - WM_GETMINMAXINFO:按最小“客户区”换算到“窗口矩形”,提供系统层最小轨迹值。
* - runEventLoop:输入事件先分发给对话框/控件,控件只登记重绘请求;真正绘制在分发或 resize 收口时统一处理。
*/
#pragma once
#include "Control.h"
#include <string>
#include <vector>
#include <memory>
#include <windows.h>
class Window
{
// —— 尺寸状态 ——(绘制尺寸与待应用尺寸分离;收口时一次性更新)
int width; // 当前有效宽(已应用到画布/控件的客户区宽)
int height; // 当前有效高(已应用到画布/控件的客户区高)
int localwidth; // 基准宽(创建时的宽度)
int localheight; // 基准高(创建是的高度)
int pendingW; // 待应用宽(WM_SIZE/拉伸中记录用)
int pendingH; // 待应用高
int minClientW; // 业务设定的最小客户区宽(用于 GETMINMAXINFO 与 SIZING 夹紧)
int minClientH; // 业务设定的最小客户区高
int windowMode = NULL; // EasyX 初始化模式(EX_SHOWCONSOLE/EX_TOPMOST/...)
bool needResizeDirty = false; // 统一收口重绘标志(置位后在事件环末尾处理)
bool isSizing = false; // 是否处于拖拽阶段(ENTER/EXIT SIZEMOVE 切换)
// —— 原生窗口句柄与子类化钩子 ——(子类化 EasyX 的窗口过程以拦截关键消息)
HWND hWnd = NULL; // EasyX 初始化后的窗口句柄
WNDPROC oldWndProc = nullptr; // 保存旧过程(CallWindowProc 回落)
bool procHooked = false; // 避免重复子类化
static LRESULT CALLBACK WndProcThunk(HWND h, UINT m, WPARAM w, LPARAM l); // 静态过程分发到 this
// —— 绘制相关 ——(是否使用合成双缓冲、窗口标题、背景等)
bool useComposited = true; // 是否启用 WS_EX_COMPOSITED(部分机器可能增加一帧观感延迟)
std::string headline; // 窗口标题文本
COLORREF wBkcolor = BLACK; // 纯色背景(无背景图时使用)
std::unique_ptr<IMAGE> background; // 背景图对象指针(存在时优先绘制)
std::string bkImageFile; // 背景图文件路径(loadimage 用)
// —— 控件/对话框 ——(容器内的普通控件与非模态对话框)
std::vector<std::unique_ptr<Control>> controls; // 普通顶层控件;绘制顺序也决定层级顺序
std::vector<std::unique_ptr<Control>> dialogs; // 非模态对话框;始终位于普通控件之上
// 顶层控件的结构变更与事件分发解耦,防止回调直接改动正在遍历的 controls。
// add/remove/clear 在事件、绘制和回调期间只入队;最外层 mutation scope 结束后再提交。
enum class ControlMutationKind { Add, Remove, Clear };
struct PendingControlMutation
{
ControlMutationKind kind = ControlMutationKind::Clear;
StellarX::ControlHandle handle{};
std::unique_ptr<Control> control;
};
std::vector<PendingControlMutation> pendingControlMutations;
int mutationScopeDepth = 0; // 允许嵌套的结构变更事务深度
// 队列提交期间保持该标志,避免提交过程中再次递归提交同一队列。
bool applyingControlMutations = false;
bool fullSceneRedrawRequested = false; // 非局部变化请求整场景重绘;在事件收口安全点消费
bool managedDispatchActive = false; // 事件分发期:控件只改状态并登记重绘,不立即画
bool managedSceneDirty = false; // 当前分发轮次是否已经登记了至少一笔托管重绘请求
struct ManagedRepaintItem // 托管重绘项:记录由哪个控件发起、需要重绘的根控件和覆盖范围(用于后续判断哪些对话框需要补画)
{
Control* root = nullptr; // 顶层重绘根(直接挂在 Window 下的控件,或 Dialog 自身)
RECT coverage{}; // 本轮脏区覆盖范围;用于判断哪些上层 Dialog 需要补画
};
std::vector<ManagedRepaintItem> managedRepaintItems; // 本轮事件分发累计的重绘项
public:
bool dialogClose = false; // 项目内使用的状态位,对话框关闭标志
mutable bool dialogOpen = false; // 项目内使用的状态位,对话框打开标志
// —— 构造/析构 ——(仅初始化成员;实际样式与子类化在 draw() 中完成)
// 创建纯色背景窗口
Window(int width, int height, int mode);
// 创建指定背景色窗口
Window(int width, int height, int mode, COLORREF bkcloc);
// 创建带标题与背景色的窗口
Window(int width, int height, int mode, COLORREF bkcloc, std::string headline);
// 析构窗口并释放背景、控件与子类化资源
~Window();
// —— 绘制与事件循环 ——(draw* 完成一次全量绘制;runEventLoop 驱动事件与统一收口)
void draw(); // 以纯色背景执行一次全量绘制
void draw(std::string pImgFile); // 以背景图执行一次全量绘制
int runEventLoop(); // 主事件循环(PeekMessage + 统一收口重绘)
// —— 背景/标题设置 ——(更换背景、背景色与标题;立即触发一次批量绘制)
void setBkImage(std::string pImgFile); // 设置窗口背景图
void setBkcolor(COLORREF c); // 设置窗口纯色背景
void setHeadline(std::string headline); // 设置窗口标题
// —— 控件/对话框管理 ——(添加到容器,或做存在性判断)
// 添加普通顶层控件并返回稳定句柄。
// 在事件回调、绘制或其它 mutation transaction 中调用时,control 会先进入待提交队列;
// 调用方仍可立即保存返回句柄,但对象要到最外层事务结束后才真正出现在 controls 中。
StellarX::ControlHandle addControl(std::unique_ptr<Control> control);
// 按句柄移除顶层控件;事务期间延迟删除,避免遍历中的对象或迭代器失效。
// 返回 true 表示目标命中且删除请求已排队/提交;返回 false 表示句柄无效或未命中。
bool removeControl(StellarX::ControlHandle handle);
// 清空所有普通顶层控件;事务期间延迟到安全点提交。
void clearControls();
// 查找当前已提交的控件;不会暴露 pending 队列中的对象。
Control* findControl(StellarX::ControlHandle handle);
// 查找当前已提交控件的只读视图;不会暴露 pending 队列中的对象。
const Control* findControl(StellarX::ControlHandle handle) const;
// 返回当前已提交控件的句柄快照;调用方不得据此推断控件之后仍然存在。
std::vector<StellarX::ControlHandle> getControlHandles() const;
// 添加非模态对话框;Window 接管生命周期并在事件安全点清理关闭对象。
void addDialog(std::unique_ptr<Control> dialogs);
//非模态对话框去重
bool hasNonModalDialogWithCaption(const std::string& caption, const std::string& message) const; // 检查是否已有同 caption/message 的非模态对话框
// —— 访问器 ——(只读接口,供外部查询当前窗口/标题/背景等)
HWND getHwnd() const; // 获取窗口句柄
int getWidth() const; // 获取当前有效客户区宽度
int getHeight() const; // 获取当前有效客户区高度
int getPendingWidth() const; // 获取待应用宽度
int getPendingHeight() const; // 获取待应用高度
std::string getHeadline() const; // 获取窗口标题
COLORREF getBkcolor() const; // 获取纯色背景
IMAGE* getBkImage() const; // 获取背景图对象
std::string getBkImageFile() const; // 获取背景图路径
// —— 尺寸调整 / 托管重绘 ——(事件阶段登记,收口阶段提交)
// 执行一次 resize 收口 + 统一重绘;只在 pending 尺寸或全场景脏标记存在时真正绘制。
void pumpResizeIfNeeded();
// 供模态 Dialog 上报宿主窗口尺寸变化;只记录待应用尺寸,实际布局/重绘由窗口收口统一完成。
void scheduleResizeFromModal(int w, int h);
// 当前是否处于“事件只改状态,不立即画”的阶段。
// 控件在该阶段应请求托管重绘,而不是直接回贴背景或立即 draw。
bool isManagedDispatchActive() const;
// 记录一笔由 source 发起的托管重绘请求。
// Window 会提升到对应 root,并在事件收口时合并 coverage 后统一提交。
void requestManagedRepaint(Control* source);
// 记录一笔托管重绘请求,并显式并入状态清理前的旧覆盖范围。
// 用于 hover/tooltip 等状态切换,保证旧区域和新区域都会被清理。
void requestManagedRepaint(Control* source, const RECT& previousCoverage);
// 在事件收口阶段提交本轮登记的 root 重绘;会补画覆盖在脏区之上的非模态对话框。
void flushManagedRepaint();
// 进入结构变更事务。允许嵌套,调用者必须保证 begin/end 成对。
// 事务内的顶层控件变更只入队,不立即修改 controls/dialogs。
void beginMutationScope();
// 离开结构变更事务。最外层退出且当前不在托管分发阶段时,会提交整棵控件树的 pending mutation。
void endMutationScope();
// 判断当前是否处于不允许立即改动控件树的阶段,包括事件分发、显式事务或队列提交过程。
bool isMutationTransactionActive() const;
// 请求下一安全点执行整场景重绘;用于 Dialog 关闭、全局覆盖关系变化等无法安全局部修复的场景。
void requestFullSceneRedraw();
private:
// 提交 Window 顶层控件队列;只允许在最外层事务安全点调用。
void flushPendingControlMutations();
// 自顶向下提交 Window、Canvas、TabControl 等容器中累积的 pending mutation。
void flushPendingMutationsInTree();
// 将全部普通控件和非模态对话框标脏,用于强制全场景重绘前统一失效缓存。
void markAllControlsDirty();
// 根据最终窗口尺寸和控件布局规格,解算并应用控件运行态矩形。
void adaptiveLayout(std::unique_ptr<Control>& c, const int finalH, const int finalW);
// resize / 初次绘制 / 对话框开关这类全局场景的整场景重绘
void redrawScene(bool forceControlsDirty, bool forceDialogsDirty);
// 绘制窗口背景;存在背景图时优先贴图,否则填充纯色背景。
void drawWindowBackground();
// 合成 WM_MOUSEMOVE,用于同步底层 hover 状态
void dispatchSyntheticMouseMoveToControls(short x, short y);
enum class OverlayMouseSyncMode
{
ClearBehindOverlay, // overlay 吞掉鼠标移动:只清理底层 hover / tooltip,不重新命中底层控件
RestoreAtCursor // overlay 关闭:按当前鼠标位置刷新底层 hover 状态
};
// overlay 层级变化后,同步底层鼠标瞬时状态;内部仍复用既有 synthetic WM_MOUSEMOVE 路径
void syncMouseStateAfterOverlayChanged(OverlayMouseSyncMode mode);
// 在事件安全点清理已关闭的非模态 Dialog,避免 dialogs 容器长期积累无效对象。
// 清理可能触发全场景重绘请求,因此必须放在遍历之外。
void sweepClosedDialogs();
// 事件回调可能改脏其他 root;flush 前补收集这些跨 root 脏子树
void collectDirtyRootsForManagedRepaint();
// 清空本轮托管重绘登记
void clearManagedRepaintState();
// 找出需要补画到最上层的对话框
void collectManagedDialogOverlays(Control* repaintRoot, const RECT& coverage, std::vector<Control*>& overlays);
};