feat: add a new awesome feature

This commit is contained in:
2025-11-19 15:14:39 +08:00
parent 5420bfd644
commit 58d4e8ab2f
23 changed files with 3360 additions and 3971 deletions
+66 -66
View File
@@ -22,7 +22,7 @@ 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())
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; // 合法双字节
@@ -30,10 +30,10 @@ static inline int gbk_char_len(const std::string& s, size_t i)
return 1; // 容错
}
static inline void rtrim_spaces_gbk(std::string& s)
static inline void rtrim_spaces_gbk(std::string& s)
{
while (!s.empty() && s.back() == ' ') s.pop_back(); // ASCII 空格
while (s.size() >= 2)
while (s.size() >= 2)
{ // 全角空格 A1 A1
unsigned char a = (unsigned char)s[s.size() - 2];
unsigned char b = (unsigned char)s[s.size() - 1];
@@ -42,26 +42,26 @@ static inline void rtrim_spaces_gbk(std::string& s)
}
}
static inline bool is_ascii_only(const std::string& s)
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)
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)
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)
if (ellW > maxW)
{ // 连 ... 都放不下
std::string e = ell;
while (!e.empty() && textwidth(LPCTSTR(e.c_str())) > maxW) e.pop_back();
@@ -71,7 +71,7 @@ static std::string ellipsize_ascii_pref(const std::string& text, int maxW)
// 先找到能放下的最长前缀
size_t i = 0, lastFit = 0;
while (i < text.size())
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;
@@ -83,7 +83,7 @@ static std::string ellipsize_ascii_pref(const std::string& text, int maxW)
// 在已适配前缀范围内,向左找最近的词边界
size_t cutPos = lastFit;
for (size_t k = lastFit; k > 0; --k)
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; }
@@ -96,14 +96,14 @@ static std::string ellipsize_ascii_pref(const std::string& text, int maxW)
}
// 中文优先策略:严格逐“字符”(1/2字节)回退;省略号用全角 "…"
static std::string ellipsize_cjk_pref(const std::string& text, int maxW, const char* ellipsis = "")
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)
if (ellW > maxW)
{ // 连省略号都放不下
std::string e = ell;
while (!e.empty() && textwidth(LPCTSTR(e.c_str())) > maxW) e.pop_back();
@@ -112,7 +112,7 @@ static std::string ellipsize_cjk_pref(const std::string& text, int maxW, const c
const int limit = maxW - ellW;
size_t i = 0, lastFit = 0;
while (i < text.size())
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;
@@ -166,8 +166,8 @@ void Button::initButton(const std::string text, StellarX::ButtonMode mode, Stell
Button::~Button()
{
if (buttonFileIMAGE)
delete buttonFileIMAGE;
if (buttonFileIMAGE)
delete buttonFileIMAGE;
buttonFileIMAGE = nullptr;
}
@@ -314,7 +314,7 @@ bool Button::handleEvent(const ExMessage& msg)
// 处理鼠标点击事件
if (msg.message == WM_LBUTTONDOWN && hover && mode != StellarX::ButtonMode::DISABLED)
{
if (mode == StellarX::ButtonMode::NORMAL)
{
click = true;
@@ -327,7 +327,7 @@ bool Button::handleEvent(const ExMessage& msg)
}
}
// NORMAL 模式:鼠标在按钮上释放时才触发点击回调,如果移出区域则取消点击状态。
// TOGGLE 模式:在释放时切换状态,并触发相应的开/关回调。
// TOGGLE 模式:在释放时切换状态,并触发相应的开/关回调。
else if (msg.message == WM_LBUTTONUP && hover && mode != StellarX::ButtonMode::DISABLED)
{
hideTooltip(); // 隐藏悬停提示
@@ -349,7 +349,7 @@ bool Button::handleEvent(const ExMessage& msg)
dirty = true;
consume = true;
refreshTooltipTextForState();
hideTooltip();
hideTooltip();
// 清除消息队列中积压的鼠标和键盘消息,防止本次点击事件被重复处理
flushmessage(EX_MOUSE | EX_KEY);
}
@@ -367,23 +367,23 @@ bool Button::handleEvent(const ExMessage& msg)
dirty = true;
}
if (tipEnabled)
if (tipEnabled)
{
if (hover && !oldHover)
if (hover && !oldHover)
{
// 刚刚进入悬停:开计时,暂不显示
tipHoverTick = GetTickCount64();
tipVisible = false;
}
if (!hover && oldHover)
if (!hover && oldHover)
{
// 刚移出:立即隐藏
hideTooltip();
}
if (hover && !tipVisible)
if (hover && !tipVisible)
{
// 到点就显示
if (GetTickCount64() - tipHoverTick >= (ULONGLONG)tipDelayMs)
if (GetTickCount64() - tipHoverTick >= (ULONGLONG)tipDelayMs)
{
tipVisible = true;
@@ -420,18 +420,18 @@ bool Button::handleEvent(const ExMessage& msg)
if (tipEnabled && tipVisible)
tipLabel.draw();
return consume;
}
void Button::setOnClickListener(const std::function<void()>&& callback)
{
this->onClickCallback = callback;
this->onClickCallback = callback;
}
void Button::setOnToggleOnListener(const std::function<void()>&& callback)
{
this->onToggleOnCallback = callback;
this->onToggleOnCallback = callback;
}
void Button::setOnToggleOffListener(const std::function<void()>&& callback)
{
@@ -443,37 +443,37 @@ void Button::setbuttonMode(StellarX::ButtonMode mode)
if (this->mode == StellarX::ButtonMode::DISABLED && mode != StellarX::ButtonMode::DISABLED)
textStyle.bStrikeOut = false;
//取值范围参考 buttMode的枚举注释
this->mode = mode;
this->mode = mode;
dirty = true; // 标记需要重绘
}
void Button::setROUND_RECTANGLEwidth(int width)
{
rouRectangleSize.ROUND_RECTANGLEwidth = width;
rouRectangleSize.ROUND_RECTANGLEwidth = width;
this->dirty = true; // 标记需要重绘
}
void Button::setROUND_RECTANGLEheight(int height)
{
rouRectangleSize.ROUND_RECTANGLEheight = height;
rouRectangleSize.ROUND_RECTANGLEheight = height;
this->dirty = true; // 标记需要重绘
}
bool Button::isClicked() const
{
return this->click;
return this->click;
}
void Button::setFillMode(StellarX::FillMode mode)
{
this->buttonFillMode = mode;
this->buttonFillMode = mode;
this->dirty = true; // 标记需要重绘
}
void Button::setFillIma(StellarX::FillStyle ima)
{
buttonFillIma = ima;
buttonFillIma = ima;
this->dirty = true;
}
@@ -484,8 +484,8 @@ void Button::setFillIma(std::string imaNAme)
delete buttonFileIMAGE;
buttonFileIMAGE = nullptr;
}
buttonFileIMAGE = new IMAGE;
loadimage(buttonFileIMAGE, imaNAme.c_str(),width,height);
buttonFileIMAGE = new IMAGE;
loadimage(buttonFileIMAGE, imaNAme.c_str(), width, height);
this->dirty = true;
}
@@ -493,7 +493,7 @@ void Button::setFillIma(std::string imaNAme)
void Button::setButtonBorder(COLORREF Border)
{
buttonBorderColor = Border;
this->dirty = true;
this->dirty = true;
}
void Button::setButtonFalseColor(COLORREF color)
@@ -504,7 +504,7 @@ void Button::setButtonFalseColor(COLORREF color)
void Button::setButtonText(const char* text)
{
this->text = std::string(text);
this->text = std::string(text);
this->text_width = textwidth(LPCTSTR(this->text.c_str()));
this->text_height = textheight(LPCTSTR(this->text.c_str()));
this->dirty = true;
@@ -526,7 +526,7 @@ void Button::setButtonText(std::string text)
void Button::setButtonShape(StellarX::ControlShape shape)
{
this->shape = shape;
this->shape = shape;
this->dirty = true;
this->needCutText = true;
}
@@ -535,7 +535,7 @@ void Button::setButtonShape(StellarX::ControlShape shape)
void Button::setButtonClick(BOOL click)
{
this->click = click;
if (mode == StellarX::ButtonMode::NORMAL && click)
{
if (onClickCallback) onClickCallback();
@@ -550,7 +550,7 @@ void Button::setButtonClick(BOOL click)
else if (!click && onToggleOffCallback) onToggleOffCallback();
dirty = true;
refreshTooltipTextForState();
hideTooltip();
hideTooltip();
// 清除消息队列中积压的鼠标和键盘消息,防止本次点击事件被重复处理
flushmessage(EX_MOUSE | EX_KEY);
}
@@ -561,52 +561,52 @@ void Button::setButtonClick(BOOL click)
std::string Button::getButtonText() const
{
return this->text;
return this->text;
}
const char* Button::getButtonText_c() const
{
return this->text.c_str();
return this->text.c_str();
}
StellarX::ButtonMode Button::getButtonMode() const
{
return this->mode;
return this->mode;
}
StellarX::ControlShape Button::getButtonShape() const
{
return this->shape;
return this->shape;
}
StellarX::FillMode Button::getFillMode() const
{
return this->buttonFillMode;
return this->buttonFillMode;
}
StellarX::FillStyle Button::getFillIma() const
{
return this->buttonFillIma;
return this->buttonFillIma;
}
IMAGE* Button::getFillImaImage() const
{
return this->buttonFileIMAGE;
return this->buttonFileIMAGE;
}
COLORREF Button::getButtonBorder() const
{
return this->buttonBorderColor;
return this->buttonBorderColor;
}
COLORREF Button::getButtonTextColor() const
{
return this->textStyle.color;
return this->textStyle.color;
}
StellarX::ControlText Button::getButtonTextStyle() const
{
return this->textStyle;
return this->textStyle;
}
int Button::getButtonWidth() const
@@ -623,11 +623,11 @@ int Button::getButtonHeight() const
bool Button::isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius)
{
double dis = sqrt(pow(mouseX - x, 2) + pow(mouseY - y, 2));
if (dis <= radius)
return true;
else
return false;
double dis = sqrt(pow(mouseX - x, 2) + pow(mouseY - y, 2));
if (dis <= radius)
return true;
else
return false;
}
bool Button::isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, int height)
@@ -636,15 +636,15 @@ bool Button::isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, i
int centerY = (y + height) / 2;
int majorAxis = (width - x) / 2;
int minorAxis = (height - y) / 2;
double dx = mouseX - centerX;
double dy = mouseY - centerY;
double normalizedDistance = (dx * dx) / (majorAxis * majorAxis) + (dy * dy) / (minorAxis * minorAxis);
double dx = mouseX - centerX;
double dy = mouseY - centerY;
double normalizedDistance = (dx * dx) / (majorAxis * majorAxis) + (dy * dy) / (minorAxis * minorAxis);
// 判断鼠标是否在椭圆内
if (normalizedDistance <= 1.0)
return true;
else
return false;
// 判断鼠标是否在椭圆内
if (normalizedDistance <= 1.0)
return true;
else
return false;
}
void Button::cutButtonText()
@@ -659,18 +659,18 @@ void Button::cutButtonText()
}
// 放不下:按语言偏好裁切(ASCII→词边界;CJK→逐字符,不撕裂双字节)
if (is_ascii_only(this->text))
if (is_ascii_only(this->text))
{
cutText = ellipsize_ascii_pref(this->text, contentW); // "..."
}
else
{
cutText = ellipsize_cjk_pref(this->text, contentW, ""); // 全角省略号
}
isUseCutText = true;
needCutText = false;
}
void Button::hideTooltip()
@@ -686,9 +686,9 @@ void Button::hideTooltip()
void Button::refreshTooltipTextForState()
{
if (tipUserOverride) return; // 用户显式设置过 tipText,保持不变
if(mode==StellarX::ButtonMode::NORMAL)
if (mode == StellarX::ButtonMode::NORMAL)
tipLabel.setText(tipTextClick);
else if(mode==StellarX::ButtonMode::TOGGLE)
else if (mode == StellarX::ButtonMode::TOGGLE)
tipLabel.setText(click ? tipTextOn : tipTextOff);
}
+197 -11
View File
@@ -17,21 +17,43 @@ void Canvas::clearAllControls()
controls.clear();
}
void Canvas::draw()
{
if (!dirty||!show)return;
if (!dirty || !show)
{
for (auto& control : controls)
if (auto c = dynamic_cast<Table*>(control.get()))
c->draw();
return;
}
saveStyle();
setlinecolor(canvasBorderClor);//设置线色
setfillcolor(canvasBkClor);//设置填充色
if(StellarX::FillMode::Null != canvasFillMode)
setfillcolor(canvasBkClor);//设置填充色
setfillstyle((int)canvasFillMode);//设置填充模式
setlinestyle((int)canvasLineStyle, canvaslinewidth);
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
saveBackground(x, y, width, height);
// 恢复背景(清除旧内容)
restBackground();
// 在绘制画布之前,先恢复并更新背景快照:
// 1. 如果已有快照,则先回贴旧快照以清除之前的内容。
// 2. 当坐标或尺寸变化,或缓存图像无效时,丢弃旧快照并重新抓取新的背景。
if (hasSnap)
{
// 恢复旧快照,清除上一次绘制
restBackground();
// 如果位置或尺寸变了,或没有有效缓存,则重新抓取
if (!saveBkImage || saveBkX != this->x || saveBkY != this->y || saveWidth != this->width || saveHeight != this->height)
{
discardBackground();
saveBackground(this->x, this->y, this->width, this->height);
}
}
else
{
// 首次绘制或没有快照时直接抓取背景
saveBackground(this->x, this->y, this->width, this->height);
}
// 再次恢复最新快照,确保绘制区域干净
restBackground();
//根据画布形状绘制
switch (shape)
{
@@ -158,9 +180,173 @@ void Canvas::setDirty(bool dirty)
void Canvas::onWindowResize()
{
Control::onWindowResize(); // 先处理自
for (auto& ch : controls) // 再转发给所有子控件
ch->onWindowResize();
// 先处理自身的快照等逻辑
Control::onWindowResize();
// 记录父容器原始尺寸(用于计算子控件的右/下边距)
int origParentW = this->localWidth;
int origParentH = this->localHeight;
// 当前容器的新尺寸
int finalW = this->width;
int finalH = this->height;
// 当前容器的新坐标(全局坐标)
int parentX = this->x;
int parentY = this->y;
// 调整每个子控件在 AnchorToEdges 模式下的位置与尺寸
for (auto& ch : controls)
{
// Only adjust when using anchor-to-edges layout
if (ch->getLayoutMode() == StellarX::LayoutMode::AnchorToEdges)
{
// Determine whether this child is a Table; tables keep their height constant
bool isTable = (dynamic_cast<Table*>(ch.get()) != nullptr);
// Unpack anchors
auto a1 = ch->getAnchor_1();
auto a2 = ch->getAnchor_2();
bool anchorLeft = (a1 == StellarX::Anchor::Left || a2 == StellarX::Anchor::Left);
bool anchorRight = (a1 == StellarX::Anchor::Right || a2 == StellarX::Anchor::Right);
bool anchorTop = (a1 == StellarX::Anchor::Top || a2 == StellarX::Anchor::Top);
bool anchorBottom = (a1 == StellarX::Anchor::Bottom || a2 == StellarX::Anchor::Bottom);
// If it's a table, treat as anchored left and right horizontally and anchored top vertically by default.
if (isTable)
{
anchorLeft = true;
anchorRight = true;
// If no explicit vertical anchor was provided, default to top.
if (!(anchorTop || anchorBottom))
{
anchorTop = true;
}
}
// Compute new X and width
int newX = ch->getX();
int newWidth = ch->getWidth();
if (anchorLeft && anchorRight)
{
// Scale horizontally relative to parent's size.
if (origParentW > 0)
{
// Maintain proportional position and size based on original local values.
double scaleW = static_cast<double>(finalW) / static_cast<double>(origParentW);
newX = parentX + static_cast<int>(ch->getLocalX() * scaleW + 0.5);
newWidth = static_cast<int>(ch->getLocalWidth() * scaleW + 0.5);
}
else
{
// Fallback: keep original
newX = parentX + ch->getLocalX();
newWidth = ch->getLocalWidth();
}
}
else if (anchorLeft && !anchorRight)
{
// Only left anchored: keep original width and left margin.
newWidth = ch->getLocalWidth();
newX = parentX + ch->getLocalX();
}
else if (!anchorLeft && anchorRight)
{
// Only right anchored: keep original width and right margin.
newWidth = ch->getLocalWidth();
int origRightDist = origParentW - (ch->getLocalX() + ch->getLocalWidth());
newX = parentX + finalW - origRightDist - newWidth;
}
else
{
// No horizontal anchor: position relative to parent's left and width unchanged.
newWidth = ch->getLocalWidth();
newX = parentX + ch->getLocalX();
}
ch->setX(newX);
ch->setWidth(newWidth);
// Compute new Y and height
int newY = ch->getY();
int newHeight = ch->getHeight();
if (isTable)
{
// Table: Height remains constant; adjust Y based on anchors.
newHeight = ch->getLocalHeight();
if (anchorTop && anchorBottom)
{
// If both top and bottom anchored, scale Y but keep height.
if (origParentH > 0)
{
double scaleH = static_cast<double>(finalH) / static_cast<double>(origParentH);
newY = parentY + static_cast<int>(ch->getLocalY() * scaleH + 0.5);
}
else
{
newY = parentY + ch->getLocalY();
}
}
else if (anchorTop && !anchorBottom)
{
// Top anchored only
newY = parentY + ch->getLocalY();
}
else if (!anchorTop && anchorBottom)
{
// Bottom anchored only
int origBottomDist = origParentH - (ch->getLocalY() + ch->getLocalHeight());
newY = parentY + finalH - origBottomDist - newHeight;
}
else
{
// No vertical anchor: default to top
newY = parentY + ch->getLocalY();
}
}
else
{
if (anchorTop && anchorBottom)
{
// Scale vertically relative to parent's size.
if (origParentH > 0)
{
double scaleH = static_cast<double>(finalH) / static_cast<double>(origParentH);
newY = parentY + static_cast<int>(ch->getLocalY() * scaleH + 0.5);
newHeight = static_cast<int>(ch->getLocalHeight() * scaleH + 0.5);
}
else
{
newY = parentY + ch->getLocalY();
newHeight = ch->getLocalHeight();
}
}
else if (anchorTop && !anchorBottom)
{
// Top anchored only: keep height constant
newHeight = ch->getLocalHeight();
newY = parentY + ch->getLocalY();
}
else if (!anchorTop && anchorBottom)
{
// Bottom anchored only: keep height and adjust Y relative to bottom
newHeight = ch->getLocalHeight();
int origBottomDist = origParentH - (ch->getLocalY() + ch->getLocalHeight());
newY = parentY + finalH - origBottomDist - newHeight;
}
else
{
// No vertical anchor: position relative to parent's top, height constant.
newHeight = ch->getLocalHeight();
newY = parentY + ch->getLocalY();
}
}
ch->setY(newY);
ch->setHeight(newHeight);
}
// Always forward the window resize event to the child (recursively).
ch->onWindowResize();
}
}
void Canvas::requestRepaint(Control* parent)
+21
View File
@@ -54,6 +54,27 @@ void Control::onWindowResize()
discardBackground();
setDirty(true);
}
void Control::setLayoutMode(StellarX::LayoutMode layoutMode_)
{
this->layoutMode = layoutMode_;
}
void Control::steAnchor(StellarX::Anchor anchor_1, StellarX::Anchor anchor_2)
{
this->anchor_1 = anchor_1;
this->anchor_2 = anchor_2;
}
StellarX::Anchor Control::getAnchor_1() const
{
return this->anchor_1;
}
StellarX::Anchor Control::getAnchor_2() const
{
return this->anchor_2;
}
StellarX::LayoutMode Control::getLayoutMode() const
{
return this->layoutMode;
}
// 保存当前的绘图状态(字体、颜色、线型等)
// 在控件绘制前调用,确保不会影响全局绘图状态
void Control::saveStyle()
+2 -2
View File
@@ -59,7 +59,7 @@ void Dialog::draw()
int ty = y + closeButtonHeight + titleToTextMargin; // 文本起始Y坐标
for (auto line:lines)
for (auto& line:lines)
{
int tx = this->x + ((this->width - textwidth(line.c_str())) / 2); // 文本起始X坐标
outtextxy(tx, ty, LPCTSTR(line.c_str()));
@@ -555,7 +555,7 @@ void Dialog::getTextSize()
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
for (auto text : lines)
for (auto& text : lines)
{
int w = textwidth(LPCTSTR(text.c_str()));
int h = textheight(LPCTSTR(text.c_str()));
+39 -16
View File
@@ -165,22 +165,37 @@ TabControl::~TabControl()
void TabControl::draw()
{
if (!dirty || !show)return;
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
saveBackground(this->x, this->y, this->width, this->height);
// 恢复背景(清除旧内容)
restBackground();
Canvas::draw();
// 在绘制 TabControl 之前,先恢复并更新背景快照:
if (hasSnap)
{
// 先回贴旧快照,清除之前的绘制
restBackground();
// 如位置或尺寸变化,丢弃旧快照并重新抓取
if (!saveBkImage || saveBkX != this->x || saveBkY != this->y || saveWidth != this->width || saveHeight != this->height)
{
discardBackground();
saveBackground(this->x, this->y, this->width, this->height);
}
}
else
{
// 首次绘制时抓取背景
saveBackground(this->x, this->y, this->width, this->height);
}
// 再次恢复最新背景,保证绘制区域干净
restBackground();
// 绘制画布背景和基本形状及其子画布控件
Canvas::draw();
for (auto& c : controls)
{
c.first->setDirty(true);
c.first->draw();
}
for (auto& c : controls)
if(c.second->IsVisible())
{
c.second->setDirty(true);
c.second->draw();
}
{
c.second->setDirty(true);
c.second->draw();
}
dirty = false;
}
@@ -216,6 +231,7 @@ void TabControl::add(std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>
controls[idx].first->setParent(this);
controls[idx].first->enableTooltip(true);
controls[idx].first->setbuttonMode(StellarX::ButtonMode::TOGGLE);
controls[idx].first->setOnToggleOnListener([this,idx]()
{
controls[idx].second->setIsVisible(true);
@@ -291,12 +307,19 @@ void TabControl::setIsVisible(bool visible)
void TabControl::onWindowResize()
{
Control::onWindowResize();
for (auto& c : controls)
{
c.first->onWindowResize();
c.second->onWindowResize();
}
// 调用基类的窗口变化处理,丢弃快照并标记脏
Control::onWindowResize();
// 根据当前 TabControl 的新尺寸重新计算页签栏和页面区域
initTabBar();
initTabPage();
// 转发窗口尺寸变化给所有页签按钮和页面
for (auto& c : controls)
{
c.first->onWindowResize();
c.second->onWindowResize();
}
// 尺寸变化后需要重绘自身
dirty = true;
}
int TabControl::getActiveIndex() const
+107 -32
View File
@@ -87,28 +87,35 @@ void Table::initTextWaH()
if (h > maxLineH)
maxLineH = h;
// 列的像素宽 = 内容宽 + 左右 padding
// 表内容总宽 = Σ(列宽 + 列间距)
int contentW = 0;
for (size_t j = 0; j < colWidths.size(); ++j)
contentW += (colWidths[j] + 2 * padX) + colGap;
// 列宽包含左右 padding:在计算完最大文本宽度后,加上 2*padX 作为单元格内边距
for (size_t j = 0; j < colWidths.size(); ++j) {
colWidths[j] += 2 * padX;
}
// 表头高 & 行高(与 drawHeader/drawTable 内部一致:+上下 padding
const int headerH = maxLineH + 2 * padY;
const int rowH = maxLineH + 2 * padY;
const int rowsH = rowH * rowsPerPage;
// 表内容总宽 = Σ(列宽 + 列间距)
int contentW = 0;
for (size_t j = 0; j < colWidths.size(); ++j)
contentW += colWidths[j] + colGap;
// 页脚:
const int pageTextH = textheight(LPCTSTR(pageNumtext.c_str()));
const int btnTextH = textheight(LPCTSTR("上一页"));
const int btnPadV = TABLE_BTN_TEXT_PAD_V;
const int btnH = btnTextH + 2 * btnPadV;
const int footerPad = TABLE_FOOTER_PAD;
const int footerH = (pageTextH > btnH ? pageTextH : btnH) + footerPad;
// 表头高 & 行高(与 drawHeader/drawTable 内部一致:+上下 padding
const int headerH = maxLineH + 2 * padY;
const int rowH = maxLineH + 2 * padY;
const int rowsH = rowH * rowsPerPage;
// 最终表宽/高:内容 + 对称边框
this->width = contentW + (border << 1);
this->height = headerH + rowsH + footerH + (border << 1);
// 页脚:
const int pageTextH = textheight(LPCTSTR(pageNumtext.c_str()));
const int btnTextH = textheight(LPCTSTR("上一页"));
const int btnPadV = TABLE_BTN_TEXT_PAD_V;
const int btnH = btnTextH + 2 * btnPadV;
const int footerPad = TABLE_FOOTER_PAD;
const int footerH = (pageTextH > btnH ? pageTextH : btnH) + footerPad;
// 最终表宽/高:内容 + 对称边框
this->width = contentW + (border << 1);
this->height = headerH + rowsH + footerH + (border << 1);
// 记录原始宽高用于锚点布局的参考;此处仅在初始化单元尺寸时重置
this->localWidth = this->width;
this->localHeight = this->height;
}
void Table::initButton()
@@ -171,6 +178,7 @@ void Table::initButton()
if (pageNum) pageNum->setDirty(true);
}
});
isNeedButtonAndPageNum = false;
}
void Table::initPageNum()
@@ -214,7 +222,7 @@ void Table::drawPageNum()
pageNumtext += "页/共";
pageNumtext += std::to_string(totalPages);
pageNumtext += "";
if (nullptr == pageNum)
if (nullptr == pageNum || isNeedButtonAndPageNum)
initPageNum();
pageNum->setText(pageNumtext);
pageNum->textStyle = this->textStyle;
@@ -226,7 +234,7 @@ void Table::drawPageNum()
void Table::drawButton()
{
if (nullptr == prevButton || nullptr == nextButton)
if ((nullptr == prevButton || nullptr == nextButton)|| isNeedButtonAndPageNum)
initButton();
this->prevButton->textStyle = this->textStyle;
@@ -242,6 +250,43 @@ void Table::drawButton()
}
void Table::setWidth(int width)
{
// 调整列宽以匹配新的表格总宽度。不修改 localWidth,避免累计误差。
// 当 width 与当前 width 不同时,根据差值平均分配到各列,余数依次累加/扣减。
const int ncols = static_cast<int>(colWidths.size());
if (ncols <= 0) {
this->width = width;
isNeedButtonAndPageNum = true;
return;
}
int diff = width - this->width;
// 基础增量:整除部分
int baseChange = diff / ncols;
int remainder = diff % ncols;
for (int i = 0; i < ncols; ++i) {
int change = baseChange;
if (remainder > 0) {
change += 1;
remainder -= 1;
} else if (remainder < 0) {
change -= 1;
remainder += 1;
}
int newWidth = colWidths[i] + change;
// 限制最小宽度为 1,防止出现负值
if (newWidth < 1) newWidth = 1;
colWidths[i] = newWidth;
}
this->width = width;
// 需要重新布局页脚元素
isNeedButtonAndPageNum = true;
}
void Table::setHeight(int height)
{
}
Table::Table(int x, int y)
:Control(x, y, 0, 0)
{
@@ -317,13 +362,27 @@ void Table::draw()
setfillstyle((int)tableFillMode);
setbkmode(TRANSPARENT);
}
//确保在绘制任何表格内容之前捕获背景
// 临时恢复样式,确保捕获正确的背景
if ((!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height)||!saveBkImage)
saveBackground(this->x, this->y, this->width, this->height);
// 恢复背景清除内容
restBackground();
// 绘制表头
// 在绘制前先恢复并更新背景快照:
// 如果已有快照且尺寸发生变化,先恢复旧快照以清除上一次绘制,然后丢弃旧快照再重新抓取新的区域。
if (hasSnap)
{
// 始终先恢复背景清除上一帧内容
restBackground();
// 当尺寸变化或缓存图像无效时,需要重新截图
if (!saveBkImage || saveWidth != this->width || saveHeight != this->height)
{
discardBackground();
saveBackground(this->x, this->y, this->width, this->height);
}
}
else
{
// 首次绘制时无背景缓存,直接抓取
saveBackground(this->x, this->y, this->width, this->height);
}
// 恢复最新的背景,保证绘制区域干净
restBackground();
// 绘制表头
dX = x;
dY = y;
@@ -365,7 +424,7 @@ bool Table::handleEvent(const ExMessage& msg)
void Table::setHeaders(std::initializer_list<std::string> headers)
{
this->headers.clear();
for (auto lis : headers)
for (auto& lis : headers)
this->headers.push_back(lis);
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
isNeedDrawHeaders = true; // 标记需要重新绘制表头
@@ -467,9 +526,12 @@ void Table::setTableBorderWidth(int width)
void Table::onWindowResize()
{
Control::onWindowResize(); // 先处理自己
prevButton->onWindowResize();
nextButton->onWindowResize();
pageNum->onWindowResize();
if (this->prevButton && this->nextButton && this->pageNum)
{
prevButton->onWindowResize();
nextButton->onWindowResize();
pageNum->onWindowResize();
}
}
int Table::getCurrentPage() const
@@ -527,4 +589,17 @@ int Table::getTableBorderWidth() const
return this->tableBorderWidth;
}
int Table::getTableWidth() const
{
int temp = 0;
for (auto& w : colWidths)
temp += w;
return temp;
}
int Table::getTableHeight() const
{
return 0;
}
+95 -28
View File
@@ -1,7 +1,7 @@
#include "Window.h"
#include "Dialog.h"
#include <graphics.h>
#include <easyx.h>
#include <algorithm>
/**
@@ -95,23 +95,23 @@ static void ApplyMinSizeOnSizing(RECT* prc, WPARAM edge, HWND hWnd, int minClien
*/
Window::Window(int w, int h, int mode)
{
minClientW = pendingW = width = w;
minClientH = pendingH = height = h;
localwidth = minClientW = pendingW = width = w;
localheight = minClientH = pendingH = height = h;
windowMode = mode;
}
Window::Window(int w, int h, int mode, COLORREF bk)
{
minClientW = pendingW = width = w;
minClientH = pendingH = height = h;
localwidth = minClientW = pendingW = width = w;
localheight = minClientH = pendingH = height = h;
windowMode = mode;
wBkcolor = bk;
}
Window::Window(int w, int h, int mode, COLORREF bk, std::string title)
{
minClientW = pendingW = width = w;
minClientH = pendingH = height = h;
localwidth = minClientW = pendingW = width = w;
localheight = minClientH = pendingH = height = h;
windowMode = mode;
wBkcolor = bk;
headline = std::move(title);
@@ -128,7 +128,7 @@ Window::~Window()
// ---------------- 原生消息钩子----------------
/**
* WndProcThunk
* WndProcThun
* 作用:替换 EasyX 的窗口过程,接管关键消息。
* 关键处理:
* - WM_ERASEBKGND:返回 1,交由自绘清屏,避免系统擦背景造成闪烁。
@@ -191,18 +191,10 @@ LRESULT CALLBACK Window::WndProcThunk(HWND h, UINT m, WPARAM w, LPARAM l)
self->needResizeDirty = true;
}
// 关键:立刻做统一收口,不用等下一条消息
self->pumpResizeIfNeeded();
// 不擦背景、不触发立即 WM_PAINT
// InvalidateRect(h, nullptr, TRUE);
// UpdateWindow(h);
InvalidateRect(h, nullptr, FALSE);
// 解冻 + 触发一次刷新(InvalidateRect TRUE 会擦背景;如需无擦背景可传 FALSE)
// 结束拉伸后不立即执行重绘,待事件循环统一收口。
// 立即解冻重绘标志,同时标记区域为有效,避免触发额外 WM_PAINT。
SendMessage(h, WM_SETREDRAW, TRUE, 0);
InvalidateRect(h, nullptr, TRUE);
UpdateWindow(h);
ValidateRect(h, nullptr);
return 0;
}
@@ -510,8 +502,8 @@ int Window::runEventLoop()
// 批量通知控件“窗口尺寸变化”,并标记重绘
for (auto& c : controls)
{
adaptiveLayout(c,finalH,finalW);
c->onWindowResize();
c->setDirty(true);
}
for (auto& d : dialogs)
{
@@ -528,9 +520,9 @@ int Window::runEventLoop()
EndBatchDraw();
// 解冻并触发一次无效化(这里 TRUE 表示会擦背景;如要避免闪白可改 FALSE)
// 解冻后标记区域有效,避免系统再次触发 WM_PAINT 覆盖自绘内容。
SendMessage(hWnd, WM_SETREDRAW, TRUE, 0);
InvalidateRect(hWnd, nullptr, TRUE);
ValidateRect(hWnd, nullptr);
}
needResizeDirty = false; // 收口完成,清标志
@@ -595,9 +587,7 @@ void Window::setHeadline(std::string title)
// 设置窗口标题(仅改文本,不触发重绘)
headline = std::move(title);
if (hWnd)
{
SetWindowText(hWnd, headline.c_str());
}
}
void Window::addControl(std::unique_ptr<Control> control)
@@ -711,13 +701,20 @@ void Window::pumpResizeIfNeeded()
loadimage(background, bkImageFile.c_str(), rc.right - rc.left, rc.bottom - rc.top, true);
putimage(0, 0, background);
}
else { setbkcolor(wBkcolor); cleardevice(); }
else
{
setbkcolor(wBkcolor);
cleardevice();
}
width = rc.right - rc.left; height = rc.bottom - rc.top;
// 通知控件/对话框
for (auto& c : controls)
{
adaptiveLayout(c, finalH, finalW);
c->onWindowResize();
}
for (auto& d : dialogs)
if (auto* dd = dynamic_cast<Dialog*>(d.get()))
dd->setInitialization(true); // 强制对话框在新尺寸下重建布局/快照
@@ -729,9 +726,10 @@ void Window::pumpResizeIfNeeded()
EndBatchDraw();
SendMessage(hWnd, WM_SETREDRAW, TRUE, 0);
// 原来是 TRUE:会擦背景再触发 WM_PAINT,容易把刚画好的层“盖掉”
// InvalidateRect(hWnd, nullptr, TRUE);
InvalidateRect(hWnd, nullptr, FALSE);
// 原实现在此调用 InvalidateRect 导致系统再次发送 WM_PAINT,从而重复绘制,
// 这里改为 ValidateRect:直接标记区域为有效,通知系统我们已完成绘制,不必再触发 WM_PAINT。
// 这样可以避免收口阶段的绘制与系统重绘叠加造成顺序错乱。
ValidateRect(hWnd, nullptr);
needResizeDirty = false;
}
@@ -750,3 +748,72 @@ void Window::scheduleResizeFromModal(int w, int h)
}
}
void Window::adaptiveLayout(std::unique_ptr<Control>& c, const int finalH, const int finalW)
{
int origParentW = this->localwidth;
int origParentH = this->localheight;
if (c->getLayoutMode() == StellarX::LayoutMode::AnchorToEdges)
{
if ((StellarX::Anchor::Left == c->getAnchor_1() && StellarX::Anchor::Right == c->getAnchor_2())
|| (StellarX::Anchor::Right == c->getAnchor_1() && StellarX::Anchor::Left == c->getAnchor_2()))
{
int origRightDist = origParentW - (c->getLocalX() + c->getLocalWidth());
int newWidth = finalW - c->getLocalX() - origRightDist;
c->setWidth(newWidth);
// 左侧距离固定,ctrl->x 保持为 localx 相对窗口左侧(父容器为窗口,偏移0)
c->setX(c->getLocalX());
}
else if ((StellarX::Anchor::Left == c->getAnchor_1() && StellarX::Anchor::NoAnchor == c->getAnchor_2())
|| (StellarX::Anchor::NoAnchor == c->getAnchor_1() && StellarX::Anchor::Left == c->getAnchor_2())
|| (StellarX::Anchor::Left == c->getAnchor_1() && StellarX::Anchor::Left == c->getAnchor_2()))
{
// 仅左锚定:宽度固定不变
c->setX(c->getLocalX());
c->setWidth(c->getLocalWidth());
}
else if ((StellarX::Anchor::Right == c->getAnchor_1() && StellarX::Anchor::NoAnchor == c->getAnchor_2())
|| (StellarX::Anchor::NoAnchor == c->getAnchor_1() && StellarX::Anchor::Right == c->getAnchor_2())
|| (StellarX::Anchor::Right == c->getAnchor_1() && StellarX::Anchor::Right == c->getAnchor_2()))
{
int origRightDist = origParentW - (c->getLocalX() + c->getLocalWidth());
c->setWidth(c->getLocalWidth()); // 宽度不变
c->setX(finalW - origRightDist - c->getWidth());
}
else if (StellarX::Anchor::NoAnchor == c->getAnchor_1() && StellarX::Anchor::NoAnchor == c->getAnchor_2())
{
c->setX(c->getLocalX());
c->setWidth(c->getLocalWidth());
}
if ((StellarX::Anchor::Top == c->getAnchor_1() && StellarX::Anchor::Bottom == c->getAnchor_2())
|| (StellarX::Anchor::Bottom == c->getAnchor_1() && StellarX::Anchor::Top == c->getAnchor_2()))
{
// 上下锚定:高度随窗口变化
int origBottomDist = origParentH - (c->getLocalY() + c->getLocalHeight());
int newHeight = finalH - c->getLocalY() - origBottomDist;
c->setHeight(newHeight);
c->setY(c->getLocalY());
}
else if ((StellarX::Anchor::Top == c->getAnchor_1() && StellarX::Anchor::NoAnchor == c->getAnchor_2())
|| (StellarX::Anchor::NoAnchor == c->getAnchor_1() && StellarX::Anchor::Top == c->getAnchor_2())
|| (StellarX::Anchor::Top == c->getAnchor_1() && StellarX::Anchor::Top == c->getAnchor_2()))
{
c->setY(c->getLocalY());
c->setHeight(c->getLocalHeight());
}
else if ((StellarX::Anchor::Bottom == c->getAnchor_1() && StellarX::Anchor::NoAnchor == c->getAnchor_2())
|| (StellarX::Anchor::NoAnchor == c->getAnchor_1() && StellarX::Anchor::Bottom == c->getAnchor_2())
|| (StellarX::Anchor::Bottom == c->getAnchor_1() && StellarX::Anchor::Bottom == c->getAnchor_2()))
{
int origBottomDist = origParentH - (c->getLocalY() + c->getLocalHeight());
c->setHeight(c->getLocalHeight());
c->setY(finalH - origBottomDist - c->getHeight());
}
else {
// 垂直无锚点:默认为顶部定位,高度固定
c->setY(c->getLocalY());
c->setHeight(c->getLocalHeight());
}
}
}