451 lines
12 KiB
C++
451 lines
12 KiB
C++
#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<Table*>(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)
|
|
{
|
|
|
|
//坐标转化
|
|
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<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)
|
|
{
|
|
if (this == parent)
|
|
{
|
|
if (!show)
|
|
return;
|
|
|
|
// 关键护栏:
|
|
// - Canvas 自己是脏的 / 没有快照 / 缓存图为空
|
|
// => 禁止局部重绘,直接升级为一次完整 draw(先把 dirty 置真,避免 draw() 早退)
|
|
if (dirty || !hasSnap || !saveBkImage)
|
|
{
|
|
SX_LOG_TRACE("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_LOG_TRACE("Dirty") << SX_T("Canvas 请求局部重绘:id=", "Canvas::requestRepaint(partial): id=") << id;
|
|
|
|
for (auto& control : controls)
|
|
if (control->isDirty() && control->IsVisible())
|
|
control->draw();
|
|
|
|
return;
|
|
}
|
|
|
|
SX_LOG_TRACE("Dirty") << SX_T("Canvas 请求根级重绘:id=", "Canvas::requestRepaint(root): id=") << id;
|
|
onRequestRepaintAsRoot();
|
|
}
|
|
|