From dde570ac3c754130c5ce1b1c52a92001e3a2f66f Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 22 Mar 2026 18:45:47 +0800 Subject: [PATCH] Initial baseline --- .gitignore | 16 + Button.cpp | 684 +++++++++++++++++++++++++++ Button.h | 187 ++++++++ Canvas.cpp | 450 ++++++++++++++++++ Canvas.h | 73 +++ Control.cpp | 207 +++++++++ Control.h | 142 ++++++ CoreTypes.h | 391 ++++++++++++++++ Dialog.cpp | 722 +++++++++++++++++++++++++++++ Dialog.h | 129 ++++++ Label.cpp | 79 ++++ Label.h | 46 ++ MessageBox.cpp | 39 ++ MessageBox.h | 43 ++ StellarX.h | 47 ++ SxLog.cpp | 443 ++++++++++++++++++ SxLog.h | 416 +++++++++++++++++ TabControl.cpp | 431 +++++++++++++++++ TabControl.h | 76 +++ Table.cpp | 674 +++++++++++++++++++++++++++ Table.h | 169 +++++++ TextBox.cpp | 247 ++++++++++ TextBox.h | 56 +++ Window.cpp | 888 ++++++++++++++++++++++++++++++++++++ Window.h | 98 ++++ imGui-easyX.sln | 31 ++ imGui-easyX.vcxproj | 174 +++++++ imGui-easyX.vcxproj.filters | 96 ++++ imGui-easyX.vcxproj.user | 6 + image/bk1.jpg | Bin 0 -> 1463584 bytes z-testDome.cpp | 711 +++++++++++++++++++++++++++++ 31 files changed, 7771 insertions(+) create mode 100644 .gitignore create mode 100644 Button.cpp create mode 100644 Button.h create mode 100644 Canvas.cpp create mode 100644 Canvas.h create mode 100644 Control.cpp create mode 100644 Control.h create mode 100644 CoreTypes.h create mode 100644 Dialog.cpp create mode 100644 Dialog.h create mode 100644 Label.cpp create mode 100644 Label.h create mode 100644 MessageBox.cpp create mode 100644 MessageBox.h create mode 100644 StellarX.h create mode 100644 SxLog.cpp create mode 100644 SxLog.h create mode 100644 TabControl.cpp create mode 100644 TabControl.h create mode 100644 Table.cpp create mode 100644 Table.h create mode 100644 TextBox.cpp create mode 100644 TextBox.h create mode 100644 Window.cpp create mode 100644 Window.h create mode 100644 imGui-easyX.sln create mode 100644 imGui-easyX.vcxproj create mode 100644 imGui-easyX.vcxproj.filters create mode 100644 imGui-easyX.vcxproj.user create mode 100644 image/bk1.jpg create mode 100644 z-testDome.cpp 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