commit dde570ac3c754130c5ce1b1c52a92001e3a2f66f Author: Codex Date: Sun Mar 22 18:45:47 2026 +0800 Initial baseline diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a21f13e --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.vs/ +x64/ +.codex-temp/ +*.obj +*.exe +*.pdb +*.ilk +*.iobj +*.ipdb +*.tlog/ +*.log +*.recipe +*.idb +*.lastbuildstate +*.VC.db +*.VC.VC.opendb diff --git a/Button.cpp b/Button.cpp new file mode 100644 index 0000000..ea8f03e --- /dev/null +++ b/Button.cpp @@ -0,0 +1,684 @@ +#include "Button.h" +#include "SxLog.h" + +Button::Button(int x, int y, int width, int height, const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape) + : Control(x, y, width, height) +{ + initButton(text, mode, shape, RGB(202, 255, 255), RGB(171, 196, 220), RGB(255, 255, 0)); +} + +Button::Button(int x, int y, int width, int height, const std::string text, COLORREF ct, COLORREF cf, StellarX::ButtonMode mode, StellarX::ControlShape shape) + : Control(x, y, width, height) +{ + initButton(text, mode, shape, ct, cf, RGB(255, 255, 0)); +} + +Button::Button(int x, int y, int width, int height, const std::string text, COLORREF ct, COLORREF cf, COLORREF ch, StellarX::ButtonMode mode, StellarX::ControlShape shape) + : Control(x, y, width, height) +{ + initButton(text, mode, shape, ct, cf, ch); +} +// ====== GBK/MBCS 安全:字符边界与省略号裁切 ====== +static inline int gbk_char_len(const std::string& s, size_t i) +{ + unsigned char b = (unsigned char)s[i]; + if (b <= 0x7F) return 1; // ASCII + if (b >= 0x81 && b <= 0xFE && i + 1 < s.size()) + { + unsigned char b2 = (unsigned char)s[i + 1]; + if (b2 >= 0x40 && b2 <= 0xFE && b2 != 0x7F) return 2; // 合法双字节 + } + return 1; // 容错 +} + +static inline void rtrim_spaces_gbk(std::string& s) +{ + while (!s.empty() && s.back() == ' ') s.pop_back(); // ASCII 空格 + while (s.size() >= 2) + { // 全角空格 A1 A1 + unsigned char a = (unsigned char)s[s.size() - 2]; + unsigned char b = (unsigned char)s[s.size() - 1]; + if (a == 0xA1 && b == 0xA1) s.resize(s.size() - 2); + else break; + } +} + +static inline bool is_ascii_only(const std::string& s) +{ + for (unsigned char c : s) if (c > 0x7F) return false; + return true; +} + +static inline bool is_word_boundary_char(unsigned char c) +{ + return c == ' ' || c == '-' || c == '_' || c == '/' || c == '\\' || c == '.' || c == ':'; +} + +// 英文优先策略:优先在“词边界”回退,再退化到逐字符;省略号为 "..." +static std::string ellipsize_ascii_pref(const std::string& text, int maxW) +{ + if (maxW <= 0) return ""; + if (textwidth(LPCTSTR(text.c_str())) <= maxW) return text; + + const std::string ell = "..."; + int ellW = textwidth(LPCTSTR(ell.c_str())); + if (ellW > maxW) + { // 连 ... 都放不下 + std::string e = ell; + while (!e.empty() && textwidth(LPCTSTR(e.c_str())) > maxW) e.pop_back(); + return e; // 可能是 ".."、"." 或 "" + } + const int limit = maxW - ellW; + + // 先找到能放下的最长前缀 + size_t i = 0, lastFit = 0; + while (i < text.size()) + { + int clen = gbk_char_len(text, i); + size_t j = text.size() < i + (size_t)clen ? text.size() : i + (size_t)clen; + int w = textwidth(LPCTSTR(text.substr(0, j).c_str())); + if (w <= limit) { lastFit = j; i = j; } + else break; + } + if (lastFit == 0) return ell; + + // 在已适配前缀范围内,向左找最近的词边界 + size_t cutPos = lastFit; + for (size_t k = lastFit; k > 0; --k) + { + unsigned char c = (unsigned char)text[k - 1]; + if (c <= 0x7F && is_word_boundary_char(c)) { cutPos = k - 1; break; } + } + + std::string head = text.substr(0, cutPos); + rtrim_spaces_gbk(head); + head += ell; + return head; +} + +// 中文优先策略:严格逐“字符”(1/2字节)回退;省略号用全角 "…" +static std::string ellipsize_cjk_pref(const std::string& text, int maxW, const char* ellipsis = "…") +{ + if (maxW <= 0) return ""; + if (textwidth(LPCTSTR(text.c_str())) <= maxW) return text; + + std::string ell = ellipsis ? ellipsis : "…"; + int ellW = textwidth(LPCTSTR(ell.c_str())); + if (ellW > maxW) + { // 连省略号都放不下 + std::string e = ell; + while (!e.empty() && textwidth(LPCTSTR(e.c_str())) > maxW) e.pop_back(); + return e; + } + const int limit = maxW - ellW; + + size_t i = 0, lastFit = 0; + while (i < text.size()) + { + int clen = gbk_char_len(text, i); + size_t j = text.size() < i + (size_t)clen ? text.size() : i + (size_t)clen; + int w = textwidth(LPCTSTR(text.substr(0, j).c_str())); + if (w <= limit) { lastFit = j; i = j; } + else break; + } + if (lastFit == 0) return ell; + + std::string head = text.substr(0, lastFit); + rtrim_spaces_gbk(head); + head += ell; + return head; +} + +void Button::setTooltipStyle(COLORREF text, COLORREF bk, bool transparent) +{ + tipLabel.textStyle.color = text; + tipLabel.setTextBkColor(bk); + tipLabel.setTextdisap(transparent); +} + +void Button::setTooltipTextsForToggle(const std::string& onText, const std::string& offText) +{ + tipTextOn = onText; + tipTextOff = offText; + tipUserOverride = true; +} + +void Button::initButton(const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape, COLORREF ct, COLORREF cf, COLORREF ch) +{ + this->id = "Button"; + this->text = text; + this->mode = mode; + this->shape = shape; + this->buttonTrueColor = ct; + this->buttonFalseColor = cf; + this->buttonHoverColor = ch; + this->click = false; + this->hover = false; + + // === Tooltip 默认:文本=按钮文本;白底黑字;不透明;用当前按钮字体样式 === + tipTextClick = tipTextOn = tipTextOff = this->text; + tipLabel.setText(tipTextClick); + tipLabel.textStyle.color = (RGB(167, 170, 172)); + tipLabel.setTextBkColor(RGB(255, 255, 255)); + tipLabel.setTextdisap(false); + tipLabel.textStyle = this->textStyle; // 复用按钮字体样式 +} + +Button::~Button() = default; + +void Button::draw() +{ + if (!dirty || !show)return; + + //保存当前样式和颜色 + saveStyle(); + + if (StellarX::ButtonMode::DISABLED == mode) //设置禁用按钮色 + { + setfillcolor(DISABLEDCOLOUR); + textStyle.bStrikeOut = true; + } + else + { + // 点击状态优先级最高,然后是悬停状态,最后是默认状态 + COLORREF col = click ? buttonTrueColor : (hover ? buttonHoverColor : buttonFalseColor); + setfillcolor(col); + } + // + //设置字体背景色透明 + setbkmode(TRANSPARENT); + //边框颜色 + setlinecolor(buttonBorderColor); + + //设置字体颜色 + settextcolor(textStyle.color); + //设置字体样式 + settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace, + textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight, + textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); + + if (needCutText) + cutButtonText(); + + //获取字符串像素高度和宽度 + if ((this->oldtext_width != this->text_width || this->oldtext_height != this->text_height) + || (-1 == oldtext_width && oldtext_height == -1)) + { + if (isUseCutText) + { + 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.get()); + if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage) + saveBackground(this->x, this->y, (this->width + bordWith), (this->height + bordHeight)); + // 恢复背景(清除旧内容) + 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; //标记按钮不需要重绘 +} +// 处理鼠标事件,检测点击和悬停状态 +// 根据按钮模式和形状进行不同的处理 +bool Button::handleEvent(const ExMessage& msg) +{ + if (!show) + return false; + + bool oldHover = hover;// 注意:只在状态变化时记录,避免 WM_MOUSEMOVE 刷屏 + bool oldClick = click; + + bool consume = false;//是否消耗事件 + const bool isMouseMessage = + msg.message == WM_MOUSEMOVE || + msg.message == WM_LBUTTONDOWN || + msg.message == WM_LBUTTONUP; + // 记录鼠标位置(用于tip定位) + if (isMouseMessage) + { + lastMouseX = msg.x; + lastMouseY = msg.y; + } + // 检测悬停状态(根据不同形状) + if (isMouseMessage) + { + switch (shape) + { + case StellarX::ControlShape::RECTANGLE: + case StellarX::ControlShape::B_RECTANGLE: + case StellarX::ControlShape::ROUND_RECTANGLE: + case StellarX::ControlShape::B_ROUND_RECTANGLE: + hover = (msg.x > x && msg.x < (x + width) && msg.y > y && msg.y < (y + height)); + break; + case StellarX::ControlShape::CIRCLE: + case StellarX::ControlShape::B_CIRCLE: + hover = isMouseInCircle(msg.x, msg.y, x + width / 2, y + height / 2, min(width, height) / 2); + break; + case StellarX::ControlShape::ELLIPSE: + case StellarX::ControlShape::B_ELLIPSE: + hover = isMouseInEllipse(msg.x, msg.y, x, y, x + width, y + height); + break; + } + } + if (hover != oldHover) + { + SX_LOGD("Button") << SX_T("悬停变化: ","hover change: ") << "id=" << id + << " text= " << text + << " " << (oldHover ? 1 : 0) << "->" << (hover ? 1 : 0); + } + // 处理鼠标点击事件 + if (msg.message == WM_LBUTTONDOWN && hover && mode != StellarX::ButtonMode::DISABLED) + { + if (mode == StellarX::ButtonMode::NORMAL) + { + click = true; + SX_LOGD("Button") << SX_T("被点击: ","lbtn - down:")<< "id = " << id <<" text = "<" << (click ? 1 : 0) + << " onCb=" << (onToggleOnCallback ? "Y" : "N") + << " offCb=" << (onToggleOffCallback ? "Y" : "N"); + + dirty = true; + consume = true; + refreshTooltipTextForState(); + hideTooltip(); + } + } + // 处理鼠标移出区域的情况 + + else if (msg.message == WM_MOUSEMOVE) + { + if (!hover && mode == StellarX::ButtonMode::NORMAL && click) + { + click = false; + dirty = true; + } + else if (hover != oldHover) + dirty = true; + } + + if (tipEnabled) + { + if (hover && !oldHover) + { + // 刚刚进入悬停:开计时,暂不显示 + tipHoverTick = GetTickCount64(); + tipVisible = false; + } + if (!hover && oldHover) + { + // 刚移出:立即隐藏 + hideTooltip(); + } + if (hover && !tipVisible) + { + // 到点就显示 + if (GetTickCount64() - tipHoverTick >= (ULONGLONG)tipDelayMs) + { + SX_LOGD("Button") << SX_T("提示信息显示: ","tooltip show:")<<" id = " << id <text, contentW, "…"); // 全角省略号 + } + isUseCutText = true; + needCutText = false; +} + +void Button::hideTooltip() +{ + if (tipVisible) + { + tipVisible = false; + tipLabel.hide(); // 还原快照+作废,防止残影 + tipHoverTick = GetTickCount64(); // 重置计时基线 + } +} + +void Button::refreshTooltipTextForState() +{ + if (tipUserOverride) return; // 用户显式设置过 tipText,保持不变 + if (mode == StellarX::ButtonMode::NORMAL) + tipLabel.setText(tipTextClick); + else if (mode == StellarX::ButtonMode::TOGGLE) + tipLabel.setText(click ? tipTextOn : tipTextOff); +} diff --git a/Button.h b/Button.h new file mode 100644 index 0000000..323ef0e --- /dev/null +++ b/Button.h @@ -0,0 +1,187 @@ +/******************************************************************************* + * @类: 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 buttonFileIMAGE; //按钮填充图像 + + std::function onClickCallback; //回调函数 + std::function onToggleOnCallback; //TOGGLE模式下的回调函数 + std::function onToggleOffCallback; //TOGGLE模式下的回调函数 + + StellarX::ControlText oldStyle = textStyle; // 按钮文字样式 + int oldtext_width = -1; + int oldtext_height = -1; + int text_width = 0; + int text_height = 0; + + // === Tooltip === + bool tipEnabled = false; // 是否启用 + bool tipVisible = false; // 当前是否显示 + bool tipFollowCursor = false; // 是否跟随鼠标 + bool tipUserOverride = false; // 是否用户自定义了tip文本 + int tipDelayMs = 1000; // 延时(毫秒) + int tipOffsetX = 12; // 相对鼠标偏移 + int tipOffsetY = 18; + ULONGLONG tipHoverTick = 0; // 开始悬停的时间戳 + int lastMouseX = 0; // 最新鼠标位置(用于定位) + int lastMouseY = 0; + + std::string tipTextClick; // NORMAL 模式下用 + std::string tipTextOn; // click==true 时用 + std::string tipTextOff; // click==false 时用 + Label tipLabel; // 直接复用Label作为提示 + +public: + StellarX::ControlText textStyle; // 按钮文字样式 + +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(); + + //绘制按钮 + void draw() override; + //按钮事件处理 + bool handleEvent(const ExMessage& msg) override; + + //设置回调函数 + void setOnClickListener(std::function callback); + //设置TOGGLE模式下被点击的回调函数 + void setOnToggleOnListener(std::function callback); + //设置TOGGLE模式下取消点击的回调函数 + void setOnToggleOffListener(std::function callback); + //设置按钮模式 + 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); + //设置按钮文本 + void setButtonText(const char* text); + void setButtonText(std::string text); + //设置按钮形状 + void setButtonShape(StellarX::ControlShape shape); + //设置按钮点击状态 + void setButtonClick(BOOL click); + + //判断按钮是否被点击 + bool isClicked() const; + + //获取按钮文字 + std::string getButtonText() const; + const char* getButtonText_c() const; + //获取按钮模式 + StellarX::ButtonMode getButtonMode() const; + //获取按钮形状 + StellarX::ControlShape getButtonShape() const; + //获取按钮填充模式 + StellarX::FillMode getFillMode() const; + //获取按钮填充图案 + StellarX::FillStyle getFillIma() const; + //获取按钮填充图像 + IMAGE* getFillImaImage() const; + //获取按钮边框颜色 + COLORREF getButtonBorder() const; + //获取按钮文字颜色 + COLORREF getButtonTextColor() const; + //获取按钮文字样式 + StellarX::ControlText getButtonTextStyle() const; +public: + // === Tooltip API=== + //设置是否启用提示框 + void enableTooltip(bool on) { tipEnabled = on; if (!on) tipVisible = false; } + //设置提示框延时 + void setTooltipDelay(int ms) { tipDelayMs = (ms < 0 ? 0 : ms); } + //设置提示框是否跟随鼠标 + void setTooltipFollowCursor(bool on) { tipFollowCursor = on; } + //设置提示框位置偏移 + void setTooltipOffset(int dx, int dy) { tipOffsetX = dx; tipOffsetY = dy; } + //设置提示框样式 + void setTooltipStyle(COLORREF text, COLORREF bk, bool transparent); + //设置提示框文本 + void setTooltipText(const std::string& s) { tipTextClick = s; tipUserOverride = true; } + void setTooltipTextsForToggle(const std::string& onText, const std::string& offText); +private: + //初始化按钮 + 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); + //获取对话框类型 + bool model() const override { return false; } + //文本截断 + void cutButtonText(); + // 统一隐藏&恢复背景 + void hideTooltip(); + // 根据当前 click 状态选择文案 + void refreshTooltipTextForState(); +}; diff --git a/Canvas.cpp b/Canvas.cpp new file mode 100644 index 0000000..1c5dff1 --- /dev/null +++ b/Canvas.cpp @@ -0,0 +1,450 @@ +#include "Canvas.h" +#include "SxLog.h" + +static bool SxIsNoisyMsg(UINT m) +{ + return m == WM_MOUSEMOVE; +} + +Canvas::Canvas() + :Control(0, 0, 100, 100) +{ + this->id = "Canvas"; +} + +Canvas::Canvas(int x, int y, int width, int height) + :Control(x, y, width, height) +{ + this->id = "Canvas"; +} + +void Canvas::setX(int x) +{ + this->x = x; + for (auto& c : controls) + { + c->onWindowResize(); + c->setX(c->getLocalX() + this->x); + } + dirty = true; +} + +void Canvas::setY(int y) +{ + this->y = y; + for (auto& c : controls) + { + c->onWindowResize(); + c->setY(c->getLocalY() + this->y); + } + dirty = true; +} + +void Canvas::clearAllControls() +{ + controls.clear(); +} + +void Canvas::draw() +{ + if (!dirty || !show) + { + for (auto& control : controls) + if (auto c = dynamic_cast(control.get())) + c->draw(); + return; + } + saveStyle(); + setlinecolor(canvasBorderClor);//设置线色 + if (StellarX::FillMode::Null != canvasFillMode) + setfillcolor(canvasBkClor);//设置填充色 + setfillstyle((int)canvasFillMode);//设置填充模式 + setlinestyle((int)canvasLineStyle, canvaslinewidth); + + // 在绘制画布之前,先恢复并更新背景快照: + // 1. 如果已有快照,则先回贴旧快照以清除之前的内容。 + // 2. 当坐标或尺寸变化,或缓存图像无效时,丢弃旧快照并重新抓取新的背景。 + int margin = canvaslinewidth > 1 ? canvaslinewidth : 1; + if (hasSnap) + { + // 恢复旧快照,清除上一次绘制 + restBackground(); + // 如果位置或尺寸变了,或没有有效缓存,则重新抓取 + if (!saveBkImage || saveBkX != this->x - margin || saveBkY != this->y - margin || saveWidth != this->width + margin * 2 || saveHeight != this->height + margin * 2) + { + discardBackground(); + saveBackground(this->x - margin, this->y - margin, this->width + margin * 2, this->height + margin * 2); + } + } + else + { + // 首次绘制或没有快照时直接抓取背景 + saveBackground(this->x - margin, this->y - margin, this->width + margin * 2, this->height + margin * 2); + } + // 再次恢复最新快照,确保绘制区域干净 + restBackground(); + //根据画布形状绘制 + switch (shape) + { + case StellarX::ControlShape::RECTANGLE: + fillrectangle(x, y, x + width, y + height);//有边框填充矩形 + break; + case StellarX::ControlShape::B_RECTANGLE: + solidrectangle(x, y, x + width, y + height);//无边框填充矩形 + break; + case StellarX::ControlShape::ROUND_RECTANGLE: + fillroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);//有边框填充圆角矩形 + break; + case StellarX::ControlShape::B_ROUND_RECTANGLE: + solidroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);//无边框填充圆角矩形 + break; + } + // 绘制所有子控件 + for (auto& control : controls) + { + control->setDirty(true); + control->draw(); + } + + restoreStyle(); + dirty = false; //标记画布不需要重绘 +} + +bool Canvas::handleEvent(const ExMessage& msg) +{ + if (!show) return false; + + bool consumed = false; + bool anyDirty = false; + Control* firstConsumer = nullptr; + + for (auto it = controls.rbegin(); it != controls.rend(); ++it) + { + Control* c = it->get(); + bool cConsumed = c->handleEvent(msg); + + if (c->isDirty()) anyDirty = true; + + if (cConsumed) + { + firstConsumer = c; + consumed = true; + break; + } + } + + if (firstConsumer && !SxIsNoisyMsg(msg.message)) + { + SX_LOGD("Event") << SX_T("Canvas 消耗消息: ","Canvas consumed: msg=") << msg.message + << SX_T("子控件"," by child")<<" id=" << firstConsumer->getId(); + } + + if (anyDirty) + { + if (!SxIsNoisyMsg(msg.message)) + SX_LOGD("Dirty") << SX_T("Canvas检测有控件为脏状态 -> 请求重绘, ","Canvas anyDirty -> requestRepaint, ")<<"id = " << id; + requestRepaint(parent); + } + + return consumed; +} + + +void Canvas::addControl(std::unique_ptr control) +{ + + //坐标转化 + control->setX(control->getLocalX() + this->x); + control->setY(control->getLocalY() + this->y); + control->setParent(this); + SX_LOGI("Canvas") + << SX_T("添加子控件:父=Canvas 子id=", "addControl: parent=Canvas childId=") + << control->getId() + << SX_T(" 相对坐标=(", " local=(") + << control->getLocalX() << "," << control->getLocalY() + << SX_T(") 绝对坐标=(", ") abs=(") + << control->getX() << "," << control->getY() + << ")"; + + + controls.push_back(std::move(control)); + dirty = true; +} + +void Canvas::setShape(StellarX::ControlShape shape) +{ + switch (shape) + { + case StellarX::ControlShape::RECTANGLE: + case StellarX::ControlShape::B_RECTANGLE: + case StellarX::ControlShape::ROUND_RECTANGLE: + case StellarX::ControlShape::B_ROUND_RECTANGLE: + this->shape = shape; + dirty = true; + break; + case StellarX::ControlShape::CIRCLE: + case StellarX::ControlShape::B_CIRCLE: + case StellarX::ControlShape::ELLIPSE: + case StellarX::ControlShape::B_ELLIPSE: + this->shape = StellarX::ControlShape::RECTANGLE; + dirty = true; + break; + } +} + +void Canvas::setCanvasfillMode(StellarX::FillMode mode) +{ + this->canvasFillMode = mode; + dirty = true; +} + +void Canvas::setBorderColor(COLORREF color) +{ + this->canvasBorderClor = color; + dirty = true; +} + +void Canvas::setCanvasBkColor(COLORREF color) +{ + this->canvasBkClor = color; + dirty = true; +} + +void Canvas::setCanvasLineStyle(StellarX::LineStyle style) +{ + this->canvasLineStyle = style; + dirty = true; +} + +void Canvas::setLinewidth(int width) +{ + this->canvaslinewidth = width; + dirty = true; +} + +void Canvas::setIsVisible(bool visible) +{ + this->show = visible; + dirty = true; + for (auto& control : controls) + { + control->setIsVisible(visible); + } + if (!visible) + this->updateBackground(); +} + +void Canvas::setDirty(bool dirty) +{ + this->dirty = dirty; + for (auto& control : controls) + control->setDirty(dirty); +} + +void Canvas::onWindowResize() +{ + // 首先处理自身的快照等逻辑 + Control::onWindowResize(); + + // 记录父容器原始尺寸(用于计算子控件的右/下边距) + int origParentW = this->localWidth; + int origParentH = this->localHeight; + + // 当前容器的新尺寸 + int finalW = this->width; + int finalH = this->height; + + // 当前容器的新坐标(全局坐标) + int parentX = this->x; + int parentY = this->y; + + // 调整每个子控件在 AnchorToEdges 模式下的位置与尺寸 + for (auto& ch : controls) + { + // Only adjust when using anchor-to-edges layout + if (ch->getLayoutMode() == StellarX::LayoutMode::AnchorToEdges) + { + // Determine whether this child is a Table; tables keep their height constant + bool isTable = (dynamic_cast(ch.get()) != nullptr); + + // Unpack anchors + auto a1 = ch->getAnchor_1(); + auto a2 = ch->getAnchor_2(); + + bool anchorLeft = (a1 == StellarX::Anchor::Left || a2 == StellarX::Anchor::Left); + bool anchorRight = (a1 == StellarX::Anchor::Right || a2 == StellarX::Anchor::Right); + bool anchorTop = (a1 == StellarX::Anchor::Top || a2 == StellarX::Anchor::Top); + bool anchorBottom = (a1 == StellarX::Anchor::Bottom || a2 == StellarX::Anchor::Bottom); + + // If it's a table, treat as anchored left and right horizontally and anchored top vertically by default. + if (isTable) + { + anchorLeft = true; + anchorRight = true; + // If no explicit vertical anchor was provided, default to top. + if (!(anchorTop || anchorBottom)) + { + anchorTop = true; + } + } + + // Compute new X and width + int newX = ch->getX(); + int newWidth = ch->getWidth(); + if (anchorLeft && anchorRight) + { + // Scale horizontally relative to parent's size. + if (origParentW > 0) + { + // Maintain proportional position and size based on original local values. + double scaleW = static_cast(finalW) / static_cast(origParentW); + newX = parentX + static_cast(ch->getLocalX() * scaleW + 0.5); + newWidth = static_cast(ch->getLocalWidth() * scaleW + 0.5); + } + else + { + // Fallback: keep original + newX = parentX + ch->getLocalX(); + newWidth = ch->getLocalWidth(); + } + } + else if (anchorLeft && !anchorRight) + { + // Only left anchored: keep original width and left margin. + newWidth = ch->getLocalWidth(); + newX = parentX + ch->getLocalX(); + } + else if (!anchorLeft && anchorRight) + { + // Only right anchored: keep original width and right margin. + newWidth = ch->getLocalWidth(); + int origRightDist = origParentW - (ch->getLocalX() + ch->getLocalWidth()); + newX = parentX + finalW - origRightDist - newWidth; + } + else + { + // No horizontal anchor: position relative to parent's left and width unchanged. + newWidth = ch->getLocalWidth(); + newX = parentX + ch->getLocalX(); + } + ch->setX(newX); + ch->setWidth(newWidth); + + // Compute new Y and height + int newY = ch->getY(); + int newHeight = ch->getHeight(); + if (isTable) + { + // Table: Height remains constant; adjust Y based on anchors. + newHeight = ch->getLocalHeight(); + if (anchorTop && anchorBottom) + { + // If both top and bottom anchored, scale Y but keep height. + if (origParentH > 0) + { + double scaleH = static_cast(finalH) / static_cast(origParentH); + newY = parentY + static_cast(ch->getLocalY() * scaleH + 0.5); + } + else + { + newY = parentY + ch->getLocalY(); + } + } + else if (anchorTop && !anchorBottom) + { + // Top anchored only + newY = parentY + ch->getLocalY(); + } + else if (!anchorTop && anchorBottom) + { + // Bottom anchored only + int origBottomDist = origParentH - (ch->getLocalY() + ch->getLocalHeight()); + newY = parentY + finalH - origBottomDist - newHeight; + } + else + { + // No vertical anchor: default to top + newY = parentY + ch->getLocalY(); + } + } + else + { + if (anchorTop && anchorBottom) + { + // Scale vertically relative to parent's size. + if (origParentH > 0) + { + double scaleH = static_cast(finalH) / static_cast(origParentH); + newY = parentY + static_cast(ch->getLocalY() * scaleH + 0.5); + newHeight = static_cast(ch->getLocalHeight() * scaleH + 0.5); + } + else + { + newY = parentY + ch->getLocalY(); + newHeight = ch->getLocalHeight(); + } + } + else if (anchorTop && !anchorBottom) + { + // Top anchored only: keep height constant + newHeight = ch->getLocalHeight(); + newY = parentY + ch->getLocalY(); + } + else if (!anchorTop && anchorBottom) + { + // Bottom anchored only: keep height and adjust Y relative to bottom + newHeight = ch->getLocalHeight(); + int origBottomDist = origParentH - (ch->getLocalY() + ch->getLocalHeight()); + newY = parentY + finalH - origBottomDist - newHeight; + } + else + { + // No vertical anchor: position relative to parent's top, height constant. + newHeight = ch->getLocalHeight(); + newY = parentY + ch->getLocalY(); + } + } + ch->setY(newY); + ch->setHeight(newHeight); + } + // Always forward the window resize event to the child (recursively). + ch->onWindowResize(); + } +} + +void Canvas::requestRepaint(Control* parent) +{ + if (this == parent) + { + if (!show) + return; + + // 关键护栏: + // - Canvas 自己是脏的 / 没有快照 / 缓存图为空 + // => 禁止局部重绘,直接升级为一次完整 draw(先把 dirty 置真,避免 draw() 早退) + if (dirty || !hasSnap || !saveBkImage) + { + SX_LOGD("Dirty") + << SX_T("Canvas 局部重绘降级为全量重绘: id=", "Canvas partial->full draw: id=") + << id + << " dirty=" << (dirty ? 1 : 0) + << " hasSnap=" << (hasSnap ? 1 : 0); + + this->dirty = true; + this->draw(); + return; + } + + SX_LOGD("Dirty") << SX_T("Canvas 请求局部重绘:id=", "Canvas::requestRepaint(partial): id=") << id; + + for (auto& control : controls) + if (control->isDirty() && control->IsVisible()) + control->draw(); + + return; + } + + SX_LOGD("Dirty") << SX_T("Canvas 请求根级重绘:id=", "Canvas::requestRepaint(root): id=") << id; + onRequestRepaintAsRoot(); +} + diff --git a/Canvas.h b/Canvas.h new file mode 100644 index 0000000..c1fa4b4 --- /dev/null +++ b/Canvas.h @@ -0,0 +1,73 @@ +/******************************************************************************* + * @类: Canvas + * @摘要: 画布容器控件,用于分组和管理子控件 + * @描述: + * 作为其他控件的父容器,提供统一的背景和边框样式。 + * 负责将事件传递给子控件并管理它们的绘制顺序。 + * + * @特性: + * - 支持四种矩形形状(普通、圆角,各有边框和无边框版本) + * - 可自定义填充模式、边框颜色和背景颜色 + * - 自动管理子控件的生命周期和事件传递 + * - 支持嵌套容器结构 + * + * @使用场景: 用于分组相关控件、实现复杂布局或作为对话框基础 + * @所属框架: 星垣(StellarX) GUI框架 + * @作者: 我在人间做废物 + *******************************************************************************/ + +#pragma once +#include "Control.h" +#include"Table.h" + +class Canvas : public Control +{ +protected: + std::vector> controls; + + 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); //背景颜色 + + // 清除所有子控件 + void clearAllControls(); +public: + Canvas(); + Canvas(int x, int y, int width, int height); + ~Canvas() {} + + void setX(int x)override; + void setY(int y)override; + + //绘制容器及其子控件 + void draw() override; + bool handleEvent(const ExMessage& msg) override; + //添加控件 + void addControl(std::unique_ptr control); + //设置容器样式 + 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; + void requestRepaint(Control* parent)override; + //获取子控件列表 + std::vector>& getControls() { return controls; } +private: + //用来检查对话框是否模态,此控件不做实现 + bool model() const override { return false; }; +}; diff --git a/Control.cpp b/Control.cpp new file mode 100644 index 0000000..62c62d3 --- /dev/null +++ b/Control.cpp @@ -0,0 +1,207 @@ +#include "Control.h" +#include "SxLog.h" +#include + +StellarX::ControlText& StellarX::ControlText::operator=(const ControlText& text) +{ + { + nHeight = text.nHeight; + nWidth = text.nWidth; + lpszFace = text.lpszFace; + color = text.color; + nEscapement = text.nEscapement; + nOrientation = text.nOrientation; + nWeight = text.nWeight; + bItalic = text.bItalic; + bUnderline = text.bUnderline; + bStrikeOut = text.bStrikeOut; + return *this; + } +} + +bool StellarX::ControlText::operator!=(const ControlText& text) +{ + if(nHeight != text.nHeight) + return true; + else if (nWidth != text.nWidth) + return true; + else if (lpszFace != text.lpszFace) + return true; + else if (color != text.color) + return true; + else if (nEscapement != text.nEscapement) + return true; + else if (nOrientation != text.nOrientation) + return true; + else if (nWeight != text.nWeight) + return true; + else if (bItalic != text.bItalic) + return true; + else if (bUnderline != text.bUnderline) + return true; + else if (bStrikeOut != text.bStrikeOut) + return true; + return false; +} +void Control::setIsVisible(bool show) +{ + SX_LOGD("Control") << SX_T("重置可见状态: id=", "setIsVisible: id=") + << id + << " show=" << (show ? 1 : 0); + + if (this->show == show) + return; + + this->show = show; + this->dirty = true; + + if (!show) + { + // 隐藏:擦除自己在屏幕上的内容,并释放快照 + this->updateBackground(); + return; + } + + // 显示:不在这里 requestRepaint(避免父容器快照未就绪时子控件抢跑 draw,污染快照) + // 仅向上标脏,让事件收口阶段由容器统一重绘。 + if (parent) + parent->setDirty(true); +} + +void Control::onWindowResize() +{ + SX_LOGD("Layout") << SX_T("尺寸变化:id=", "onWindowResize: id=") << id + << SX_T(" -> 丢背景快照 + 标脏", " -> discardSnap + dirty"); + + // 自己:丢快照 + 标脏 + discardBackground(); + setDirty(true); +} +void Control::setLayoutMode(StellarX::LayoutMode layoutMode_) +{ + this->layoutMode = layoutMode_; +} +void Control::setAnchor(StellarX::Anchor anchor_1, StellarX::Anchor anchor_2) +{ + this->anchor_1 = anchor_1; + this->anchor_2 = anchor_2; +} +StellarX::Anchor Control::getAnchor_1() const +{ + return this->anchor_1; +} +StellarX::Anchor Control::getAnchor_2() const +{ + return this->anchor_2; +} +StellarX::LayoutMode Control::getLayoutMode() const +{ + return this->layoutMode; +} +// 保存当前的绘图状态(字体、颜色、线型等) +// 在控件绘制前调用,确保不会影响全局绘图状态 +void Control::saveStyle() +{ + + gettextstyle(¤tFont); // 获取当前字体样式 + currentColor = gettextcolor(); // 获取当前字体颜色 + currentBorderColor = getlinecolor(); //保存当前边框颜色 + getlinestyle(¤tLineStyle); //保存当前线型 + currentBkColor = getfillcolor(); //保存当前填充色 +} +// 恢复之前保存的绘图状态 +// 在控件绘制完成后调用,恢复全局绘图状态 +void Control::restoreStyle() +{ + settextstyle(¤tFont); // 恢复默认字体样式 + settextcolor(currentColor); // 恢复默认字体颜色 + setfillcolor(currentBkColor); + setlinestyle(¤tLineStyle); + setlinecolor(currentBorderColor); + setfillstyle(BS_SOLID);//恢复填充 +} + +void Control::requestRepaint(Control* parent) +{ + // 说明: + // - 常规路径:子控件调用 requestRepaint(this->parent),然后 parent 负责局部重绘(Canvas/TabControl override) + // - 兜底路径:如果某个“容器控件”没 override requestRepaint,就会出现 parent==this 的递归风险 + // 此时我们改为向更上层冒泡,直到根重绘。 + if (parent == this) + { + SX_LOGW("Dirty") + << SX_T("requestRepaint(默认容器兜底):id=", "requestRepaint(default-container-fallback): id=") + << id + << SX_T(",parent==this,向上层 parent 继续冒泡", " parent==this, bubble to upper parent"); + + if (this->parent) this->parent->requestRepaint(this->parent); + else onRequestRepaintAsRoot(); + return; + } + + SX_LOGD("Dirty") << SX_T("请求重绘:id=","requestRepaint: id=") << id << " parent=" << (parent ? parent->getId() : "null"); + + if (parent) parent->requestRepaint(parent); // 交给容器处理(容器可局部重绘) + else onRequestRepaintAsRoot(); // 根兜底 +} + +void Control::onRequestRepaintAsRoot() +{ + SX_LOGI("Dirty") + << SX_T("触发根重绘:id=", "onRequestRepaintAsRoot: id=") << id + << SX_T("(从根节点开始重画)", " (root repaint)"); + + + discardBackground(); + setDirty(true); + draw(); // 只有“无父”时才允许立即画,不会被谁覆盖 +} + +void Control::saveBackground(int x, int y, int w, int h) +{ + + if (w <= 0 || h <= 0) return; + saveBkX = x; saveBkY = y; saveWidth = w; saveHeight = h; + if (saveBkImage) + { + //尺寸变了才重建,避免反复 new/delete + if (saveBkImage->getwidth() != w || saveBkImage->getheight() != h) + { + SX_LOGD("Snap") <(w, h); + + SetWorkingImage(nullptr); // ★抓屏幕 + getimage(saveBkImage.get(), x, y, w, h); + hasSnap = true; +} + +void Control::restBackground() +{ + if (!hasSnap || !saveBkImage) return; + // 直接回贴屏幕(与抓取一致) + SetWorkingImage(nullptr); + putimage(saveBkX, saveBkY, saveBkImage.get()); +} + +void Control::discardBackground() +{ + if (saveBkImage) + { + restBackground(); + SX_LOGD("Snap") << SX_T("丢弃背景快照:id=","discardBackground: id=") << id << " hasSnap=" << (hasSnap ? 1 : 0); + saveBkImage.reset(); + } + hasSnap = false; saveWidth = saveHeight = 0; +} + +void Control::updateBackground() +{ + restBackground(); + discardBackground(); +} diff --git a/Control.h b/Control.h new file mode 100644 index 0000000..f40aaab --- /dev/null +++ b/Control.h @@ -0,0 +1,142 @@ +/******************************************************************************* + * @类: 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 +#include +#include +#include +#undef MessageBox +#include +#include +#include +#include "CoreTypes.h" + +class Control +{ +protected: + std::string id; // 控件ID + int localx, x, localy, y; // 左上角坐标 + int localWidth, width, localHeight, height; // 控件尺寸 + Control* parent = nullptr; // 父控件 + bool dirty = true; // 是否重绘 + bool show = true; // 是否显示 + + /* == 布局模式 == */ + StellarX::LayoutMode layoutMode = StellarX::LayoutMode::Fixed; // 布局模式 + StellarX::Anchor anchor_1 = StellarX::Anchor::Top; // 锚点 + StellarX::Anchor anchor_2 = StellarX::Anchor::Right; // 锚点 + + /* == 背景快照 == */ + std::unique_ptr 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; + 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: + + virtual ~Control() + { + discardBackground(); + } +protected: + //向上请求重绘 + virtual void requestRepaint(Control* parent); + //根控件/无父时触发重绘 + virtual void onRequestRepaintAsRoot(); +protected: + //保存背景快照 + virtual void saveBackground(int x, int y, int w, int h); + // putimage 回屏 + virtual void restBackground(); + // 释放快照(窗口重绘/尺寸变化后必须作废) + void discardBackground(); +public: + //释放快照重新保存,在尺寸变化时更新背景快照避免尺寸变化导致显示错位 + void updateBackground(); + //窗口变化丢快照 + virtual void onWindowResize(); + // 获取位置和尺寸 + int getX() const { return x; } + int 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; } + + 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; } + + virtual void setX(int x) { this->x = x; dirty = true; } + virtual void setY(int y) { this->y = y; dirty = true; } + virtual void setWidth(int width) { this->width = width; dirty = true; } + virtual void setHeight(int height) { this->height = height; dirty = true; } +public: + + virtual void draw() = 0; + virtual bool handleEvent(const ExMessage& msg) = 0;//返回true代表事件已消费 + //设置是否显示 + virtual void setIsVisible(bool show); + //设置父容器指针 + void setParent(Control* parent) { this->parent = parent; } + //设置是否重绘 + virtual void setDirty(bool dirty) { this->dirty = dirty; } + //检查控件是否可见 + bool IsVisible() const { return show; }; + //获取控件id + std::string getId() const { return id; } + //检查是否为脏 + bool isDirty() { return dirty; } + //用来检查对话框是否模态,其他控件不用实现 + virtual bool model()const = 0; + //布局 + void setLayoutMode(StellarX::LayoutMode layoutMode_); + void setAnchor(StellarX::Anchor anchor_1, StellarX::Anchor anchor_2); + StellarX::Anchor getAnchor_1() const; + StellarX::Anchor getAnchor_2() const; + StellarX::LayoutMode getLayoutMode() const; +protected: + void saveStyle(); + void restoreStyle(); +}; diff --git a/CoreTypes.h b/CoreTypes.h new file mode 100644 index 0000000..3225ecb --- /dev/null +++ b/CoreTypes.h @@ -0,0 +1,391 @@ +/******************************************************************************* + * @文件: CoreTypes.h + * @摘要: 星垣(StellarX)框架核心类型定义文件 + * @描述: + * 集中定义框架中使用的所有枚举类型和结构体,确保类型一致性。 + * 这是框架的类型系统基础,所有控件都依赖于此文件中定义的类型。 + * + * @作者: 我在人间做废物 + * @日期: September 2025 + ******************************************************************************/ +#pragma once + +#include "easyx.h" + + /** + * @命名空间: StellarX + * + * @详细说明: + * 集中定义框架中使用的所有枚举类型和结构体,确保类型一致性。 + * 这是框架的类型系统基础,所有控件都依赖于此文件中定义的类型 + * + * @使用示例: + * StellarX::FillStyle::Horizontal - 填充样式 + * + * @备注: + * 不用单独包含此头文件,已在StellarX.h中包含,包含唯一对外头文件即可 + */ +namespace StellarX +{ + /** + * @枚举类名称: 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则自适应 + * LPCTSTR lpszFace = "宋体"; - 字体名称 + * 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则自适应 + LPCTSTR lpszFace = "微软雅黑"; //- 字体名称 + 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); + ControlText& operator=(const ControlText& 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; //构成圆角矩形的圆角的椭圆的高度。 + }; + + // 消息框类型 + enum class MessageBoxType + { + OK, // 只有确定按钮 + OKCancel, // 确定和取消按钮 + YesNo, // 是和否按钮 + YesNoCancel, // 是、否和取消按钮 + RetryCancel, // 重试和取消按钮 + AbortRetryIgnore, // 中止、重试和忽略按钮 + }; + + // 消息框返回值 + enum class MessageBoxResult + { + OK = 1, // 确定按钮 + Cancel = 2, // 取消按钮 + Yes = 6, // 是按钮 + No = 7, // 否按钮 + Abort = 3, // 中止按钮 + Retry = 4, // 重试按钮 + Ignore = 5 // 忽略按钮 + }; +#if 0 //布局管理器相关 —待实现— + /* + * + *@枚举名称: LayoutKind + * @功能描述 : 定义布局管理类型 + * + *@详细说明 : + * 根据布局管理类型,控件可以有不同的布局方式。 + * 用户可以在具体情况下设置布局管理类型。 + * + * @取值说明 : + * Absolute:不管,保持子控件自己的坐标(向后兼容)。 + * HBox: 水平方向排队;支持固定宽、权重分配、对齐、拉伸。 + * VBox: 竖直方向排队;同上。 + * Grid(网格):按行列摆;支持固定/自适应/权重行列;支持跨行/跨列;单元内对齐/拉伸。 + * + */ + // 布局类型 + enum class LayoutKind + { + Absolute = 1, + HBox, + VBox, + Grid, + Flow, + Stack + }; + + // 布局参数 + struct LayoutParams + { + // 边距左、右、上、下 + int marginL = 0, marginR = 0, marginT = 0, marginB = 0; + // 固定尺寸(>=0 强制;-1 用控件当前尺寸) + int fixedW = -1, fixedH = -1; + // 主轴权重(HBox=宽度、VBox=高度、Grid见下) + float weight = 0.f; + // 对齐(非拉伸时生效) + enum Align { Start = 0, Center = 1, End = 2, Stretch = 3 }; + int alignX = Start; // HBox: 次轴=Y;VBox: 次轴=X;Grid: 单元内 + int alignY = Start; // Grid :控制单元内垂直(HBox / VBox通常只用 alignX) + // Grid 专用(可先不做) + int gridRow = 0, gridCol = 0, rowSpan = 1, colSpan = 1; + // Flow 专用(可先不做) + int flowBreak = 0; // 1=强制换行 + }; +#endif + + /* + * @枚举名称: TabPlacement + * @功能描述: 定义了选项卡页签的不同位置 + * + * @详细说明: + * 根据选项卡页签的位置,选项卡页签可以有不同的布局方式。 + * + * @成员说明: + * Top, - 选项卡页签位于顶部 + * Bottom, - 选项卡页签位于底部 + * Left, - 选项卡页签位于左侧 + * Right - 选项卡页签位于右侧 + * + * @使用示例: + * TabPlacement placement = TabPlacement::Top; + */ + enum class TabPlacement + { + Top, + Bottom, + Left, + Right + }; + /* + * @枚举名称: LayoutMode + * @功能描述: 定义了两种布局模式 + * + * @详细说明: + * 根据不同模式,在窗口拉伸时采用不同的布局策略 + * + * @成员说明: + * Fixed, - 固定布局 + * AnchorToEdges - 锚定布局 + * + * @使用示例: + * LayoutMode mode = LayoutMode::Fixed; + */ + enum class LayoutMode + { + Fixed, + AnchorToEdges + }; + /* + * @枚举名称: Anchor + * @功能描述: 定义了控件相对于窗口锚定的位置 + * + * @详细说明: + * 根据不同的锚定位置,有不同的拉伸策略 + * + * @成员说明: + * Top, - 锚定上边,控件上边与窗口上侧距离保持不变 + * Bottom, - 锚定底边,控件底边与窗口底边距离保持不变 + * Left, - 锚定左边,控件左边与窗口左侧距离保持不变 + * Right - 锚定右边,控件上边与窗口右侧距离保持不变 + * + * @使用示例: + * Anchor a = Anchor::Top; + */ + enum class Anchor + { + NoAnchor = 0, + Left = 1, + Right, + Top, + Bottom + }; +} \ No newline at end of file diff --git a/Dialog.cpp b/Dialog.cpp new file mode 100644 index 0000000..61e99b7 --- /dev/null +++ b/Dialog.cpp @@ -0,0 +1,722 @@ +#include "Dialog.h" +#include "SxLog.h" + +Dialog::Dialog(Window& h, std::string text, std::string message, StellarX::MessageBoxType type, bool modal) + : Canvas(), message(message), type(type), modal(modal), hWnd(h), titleText(text) +{ + this->id = "Dialog"; + show = false; +} + +Dialog::~Dialog() +{ +} + +void Dialog::draw() +{ + if (!show) + { + // 如果对话框不可见且需要清理,执行清理 + if (pendingCleanup && !isCleaning) + { + performDelayedCleanup(); + } + return; + } + // 如果需要初始化,则执行初始化 + if (needsInitialization && show) + { + initDialogSize(); + needsInitialization = false; + } + + if (dirty && show) + { + // 保存当前绘图状态 + saveStyle(); + + Canvas::setBorderColor(this->borderColor); + Canvas::setLinewidth(BorderWidth); + Canvas::setCanvasBkColor(this->backgroundColor); + Canvas::setShape(StellarX::ControlShape::ROUND_RECTANGLE); + + Canvas::draw(); + + //绘制消息文本 + settextcolor(textStyle.color); + + //设置字体样式 + settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace, + textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight, + textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); + + int ty = y + closeButtonHeight + titleToTextMargin; // 文本起始Y坐标 + for (auto& line : lines) + { + int tx = this->x + ((this->width - textwidth(line.c_str())) / 2); // 文本起始X坐标 + outtextxy(tx, ty, LPCTSTR(line.c_str())); + ty = ty + textheight(LPCTSTR(line.c_str())) + 5; // 每行文本高度加5像素间距 + } + + // 恢复绘图状态 + restoreStyle(); + + dirty = false; + } +} + +bool Dialog::handleEvent(const ExMessage& msg) +{ + bool consume = false; + if (!show) + { + if (pendingCleanup && !isCleaning) + { + performDelayedCleanup(); + } + return false; + } + + // 如果正在清理或标记为待清理,则不处理事件 + if (pendingCleanup || isCleaning) + return false; + // 模态对话框不允许点击外部区域 + // 模态对话框:点击对话框外部区域时,发出提示音(\a)并吞噬该事件,不允许操作背景内容。 + if (modal && msg.message == WM_LBUTTONUP && + (msg.x < x || msg.x > x + width || msg.y < y || msg.y > y + height)) + { + std::cout << "\a" << std::endl; + return true; + } + + // 将事件传递给子控件处理 + if (!consume) + consume = Canvas::handleEvent(msg); + + // 每次事件处理后检查是否需要执行延迟清理 + if (pendingCleanup && !isCleaning) + performDelayedCleanup(); + return consume; +} + +void Dialog::SetTitle(const std::string& title) +{ + this->titleText = title; + if (this->title) + { + this->title->setText(title); + } + dirty = true; +} + +void Dialog::SetMessage(const std::string& message) +{ + this->message = message; + splitMessageLines(); + getTextSize(); + dirty = true; +} + +void Dialog::SetType(StellarX::MessageBoxType type) +{ + this->type = type; + // 重新初始化按钮 + initButtons(); + dirty = true; +} + +void Dialog::SetModal(bool modal) +{ + this->modal = modal; +} + +void Dialog::SetResult(StellarX::MessageBoxResult result) +{ + this->result = result; +} + +StellarX::MessageBoxResult Dialog::GetResult() const +{ + return this->result; +} + +bool Dialog::model() const +{ + return modal; +} + +void Dialog::Show() +{ + if (pendingCleanup) + performDelayedCleanup(); + SX_LOGI("Dialog") << SX_T("对话框弹出:是否模态=","Dialog::Show: modal=") << (modal ? 1 : 0); + + show = true; + dirty = true; + needsInitialization = true; + close = false; + shouldClose = false; + + hWnd.dialogOpen = true;// 通知窗口有对话框打开 + + if (modal) + { + // 模态对话框需要阻塞当前线程直到对话框关闭 + if (modal) + { + // 记录当前窗口客户区尺寸,供轮询对比 + RECT rc0; + GetClientRect(hWnd.getHwnd(), &rc0); + int lastW = rc0.right - rc0.left; + int lastH = rc0.bottom - rc0.top; + + while (show && !close) + { + // ① 轮询窗口尺寸(不依赖 WM_SIZE) + RECT rc; + GetClientRect(hWnd.getHwnd(), &rc); + const int cw = rc.right - rc.left; + const int ch = rc.bottom - rc.top; + + if (cw != lastW || ch != lastH) + { + lastW = cw; + lastH = ch; + SX_LOGD("Resize") <draw(); // 注意:不要 requestRepaint(parent),只画自己 + EndBatchDraw(); + dirty = false; + } + + Sleep(10); + } + + if (pendingCleanup && !isCleaning) + performDelayedCleanup(); + } + else + { + // 非模态仍由主循环托管 + dirty = true; + } + + // 模态对话框关闭后执行清理 + if (pendingCleanup && !isCleaning) + performDelayedCleanup(); + } + else + // 非模态对话框只需标记为可见,由主循环处理 + dirty = true; +} + +void Dialog::Close() +{ + if (!show) return; + + show = false; + close = true; + dirty = true; + pendingCleanup = true; // 只标记需要清理,不立即执行 + + // 工厂模式下非模态触发回调 返回结果 + if (resultCallback && !modal) + resultCallback(this->result); +} + +void Dialog::setInitialization(bool init) +{ + if (init) + { + initDialogSize(); + saveBackground((x - BorderWidth), (y - BorderWidth), (width + 2 * BorderWidth), (height + 2 * BorderWidth)); + this->dirty = true; + } +} + +void Dialog::initButtons() +{ + controls.clear(); + + switch (this->type) + { + case StellarX::MessageBoxType::OK: // 只有确定按钮 + { + auto okbutton = createDialogButton((this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2), + ((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2), + "确定" + ); + okbutton->setOnClickListener([this]() + { + this->SetResult(StellarX::MessageBoxResult::OK); + this->hWnd.dialogClose = true; + this->Close(); }); + + okbutton->textStyle = this->textStyle; + + this->addControl(std::move(okbutton)); + } + break; + case StellarX::MessageBoxType::OKCancel: // 确定和取消按钮 + { + auto okButton = createDialogButton( + (this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2), + ((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2), + "确定" + ); + okButton->setOnClickListener([this]() + { + this->SetResult(StellarX::MessageBoxResult::OK); + this->hWnd.dialogClose = true; + this->Close(); }); + + auto cancelButton = createDialogButton( + (okButton.get()->getX() + okButton.get()->getWidth() + buttonMargin), + okButton.get()->getY(), + "取消" + ); + cancelButton->setOnClickListener([this]() + { + this->SetResult(StellarX::MessageBoxResult::Cancel); + this->hWnd.dialogClose = true; + this->Close(); }); + + okButton->textStyle = this->textStyle; + cancelButton->textStyle = this->textStyle; + + this->addControl(std::move(okButton)); + this->addControl(std::move(cancelButton)); + } + break; + case StellarX::MessageBoxType::YesNo: // 是和否按钮 + { + auto yesButton = createDialogButton( + (this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2), + ((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2), + "是" + ); + yesButton->setOnClickListener([this]() + { + this->SetResult(StellarX::MessageBoxResult::Yes); + this->hWnd.dialogClose = true; + this->Close(); }); + + auto noButton = createDialogButton( + (yesButton.get()->getX() + yesButton.get()->getWidth() + buttonMargin), + yesButton.get()->getY(), + "否" + ); + noButton->setOnClickListener([this]() + { + this->SetResult(StellarX::MessageBoxResult::No); + this->hWnd.dialogClose = true; + this->Close(); }); + + yesButton->textStyle = this->textStyle; + noButton->textStyle = this->textStyle; + + this->addControl(std::move(yesButton)); + this->addControl(std::move(noButton)); + } + break; + case StellarX::MessageBoxType::YesNoCancel: // 是、否和取消按钮 + { + auto yesButton = createDialogButton( + (this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2), + ((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2), + "是" + ); + yesButton->setOnClickListener([this]() + { + this->SetResult(StellarX::MessageBoxResult::Yes); + this->hWnd.dialogClose = true; + this->Close(); }); + + auto noButton = createDialogButton( + yesButton.get()->getX() + yesButton.get()->getWidth() + buttonMargin, + yesButton.get()->getY(), + "否" + ); + noButton->setOnClickListener([this]() + { + this->SetResult(StellarX::MessageBoxResult::No); + this->hWnd.dialogClose = true; + this->Close(); }); + + auto cancelButton = createDialogButton( + noButton.get()->getX() + noButton.get()->getWidth() + buttonMargin, + noButton.get()->getY(), + "取消" + ); + cancelButton->setOnClickListener([this]() + { + this->SetResult(StellarX::MessageBoxResult::Cancel); + this->hWnd.dialogClose = true; + this->Close(); }); + + yesButton->textStyle = this->textStyle; + noButton->textStyle = this->textStyle; + cancelButton->textStyle = this->textStyle; + + this->addControl(std::move(yesButton)); + this->addControl(std::move(noButton)); + this->addControl(std::move(cancelButton)); + } + break; + case StellarX::MessageBoxType::RetryCancel: // 重试和取消按钮 + { + auto retryButton = createDialogButton( + (this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2), + ((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2), + "重试" + ); + retryButton->setOnClickListener([this]() + { + this->SetResult(StellarX::MessageBoxResult::Retry); + this->hWnd.dialogClose = true; + this->Close(); }); + + auto cancelButton = createDialogButton( + retryButton.get()->getX() + retryButton.get()->getWidth() + buttonMargin, + retryButton.get()->getY(), + "取消" + ); + cancelButton->setOnClickListener([this]() + { + this->SetResult(StellarX::MessageBoxResult::Cancel); + this->hWnd.dialogClose = true; + this->Close(); }); + + retryButton->textStyle = this->textStyle; + cancelButton->textStyle = this->textStyle; + + this->addControl(std::move(retryButton)); + this->addControl(std::move(cancelButton)); + } + break; + case StellarX::MessageBoxType::AbortRetryIgnore: // 中止、重试和忽略按钮 + { + auto abortButton = createDialogButton( + (this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2), + ((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2), + "中止" + ); + abortButton->setOnClickListener([this]() + { + this->SetResult(StellarX::MessageBoxResult::Abort); + this->hWnd.dialogClose = true; + this->Close(); + }); + auto retryButton = createDialogButton( + abortButton.get()->getX() + abortButton.get()->getWidth() + buttonMargin, + abortButton.get()->getY(), + "重试" + ); + retryButton->setOnClickListener([this]() + { + this->SetResult(StellarX::MessageBoxResult::Retry); + this->hWnd.dialogClose = true; + this->Close(); + }); + auto ignoreButton = createDialogButton( + retryButton.get()->getX() + retryButton.get()->getWidth() + buttonMargin, + retryButton.get()->getY(), + "忽略" + ); + ignoreButton.get()->setOnClickListener([this]() + { + this->SetResult(StellarX::MessageBoxResult::Ignore); + this->hWnd.dialogClose = true; + this->Close(); + }); + + abortButton->textStyle = this->textStyle; + retryButton->textStyle = this->textStyle; + ignoreButton->textStyle = this->textStyle; + + this->addControl(std::move(abortButton)); + this->addControl(std::move(retryButton)); + this->addControl(std::move(ignoreButton)); + } + break; + } +} + +void Dialog::initCloseButton() +{ + //初始化关闭按钮 + auto but = std::make_unique