384 lines
10 KiB
C++
384 lines
10 KiB
C++
#include "Canvas.h"
|
|
|
|
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;
|
|
|
|
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
|
{
|
|
consumed |= it->get()->handleEvent(msg);
|
|
if (it->get()->isDirty()) anyDirty = true;
|
|
}
|
|
if (anyDirty) 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);
|
|
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);
|
|
control->setDirty(true);
|
|
}
|
|
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)
|
|
{
|
|
for (auto& control : controls)
|
|
if (control->isDirty() && control->IsVisible())
|
|
control->draw();
|
|
}
|
|
else
|
|
onRequestRepaintAsRoot();
|
|
} |