Save pre-batched-redraw snapshot

This commit is contained in:
Codex
2026-04-07 16:23:40 +08:00
parent 7f8431a18c
commit b07a4ec370
8 changed files with 142 additions and 54 deletions
+11 -9
View File
@@ -276,9 +276,11 @@ bool Button::handleEvent(const ExMessage& msg)
{ {
if (!show) if (!show)
return false; return false;
resetEventVisualChanged();
bool oldHover = hover;// 注意:只在状态变化时记录,避免 WM_MOUSEMOVE 刷屏 bool oldHover = hover;// 注意:只在状态变化时记录,避免 WM_MOUSEMOVE 刷屏
bool oldClick = click; bool oldClick = click;
const bool oldTipVisible = tipVisible;
bool consume = false;//是否消耗事件 bool consume = false;//是否消耗事件
const bool isMouseMessage = const bool isMouseMessage =
@@ -312,12 +314,6 @@ bool Button::handleEvent(const ExMessage& msg)
break; break;
} }
} }
if (hover != oldHover)
{
SX_LOG_TRACE("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 (msg.message == WM_LBUTTONDOWN && hover && mode != StellarX::ButtonMode::DISABLED)
{ {
@@ -396,8 +392,6 @@ bool Button::handleEvent(const ExMessage& msg)
// 到点就显示 // 到点就显示
if (GetTickCount64() - tipHoverTick >= (ULONGLONG)tipDelayMs) if (GetTickCount64() - tipHoverTick >= (ULONGLONG)tipDelayMs)
{ {
SX_LOG_TRACE("Button") << SX_T("提示信息显示: ","tooltip show:")<<" id = " << id <<SX_T("延时时间: ", " delayMs = ") << tipDelayMs;
tipVisible = true; tipVisible = true;
// 定位(跟随鼠标 or 相对按钮) // 定位(跟随鼠标 or 相对按钮)
@@ -424,8 +418,16 @@ bool Button::handleEvent(const ExMessage& msg)
} }
// 如果状态发生变化,标记需要重绘 // 如果状态发生变化,标记需要重绘
if (hover != oldHover || click != oldClick) const bool stateChanged = (hover != oldHover || click != oldClick);
if (stateChanged)
dirty = true; dirty = true;
const bool tipVisibilityChanged = (tipVisible != oldTipVisible);
// 鼠标命中按钮区域,或按钮自身状态因此发生变化时,吞掉该事件。
// 这样可以避免被遮挡/重叠的下层控件继续收到同一事件并把自己重绘到上层。
if (isMouseMessage && (hover || oldHover || click != oldClick))
consume = true;
markEventVisualChanged(stateChanged || tipVisibilityChanged);
// 如果需要重绘,立即执行 // 如果需要重绘,立即执行
if (dirty) if (dirty)
+20 -2
View File
@@ -6,6 +6,19 @@ static bool SxIsNoisyMsg(UINT m)
return m == WM_MOUSEMOVE; return m == WM_MOUSEMOVE;
} }
static const char* SxCanvasMsgName(UINT m)
{
switch (m)
{
case WM_MOUSEMOVE: return "WM_MOUSEMOVE";
case WM_LBUTTONDOWN: return "WM_LBUTTONDOWN";
case WM_LBUTTONUP: return "WM_LBUTTONUP";
case WM_KEYDOWN: return "WM_KEYDOWN";
case WM_KEYUP: return "WM_KEYUP";
default: return "WM_UNKNOWN";
}
}
Canvas::Canvas() Canvas::Canvas()
:Control(0, 0, 100, 100) :Control(0, 0, 100, 100)
{ {
@@ -113,9 +126,11 @@ void Canvas::draw()
bool Canvas::handleEvent(const ExMessage& msg) bool Canvas::handleEvent(const ExMessage& msg)
{ {
if (!show) return false; if (!show) return false;
resetEventVisualChanged();
bool consumed = false; bool consumed = false;
bool anyDirty = false; bool anyDirty = false;
bool anyVisualChanged = false;
Control* firstConsumer = nullptr; Control* firstConsumer = nullptr;
for (auto it = controls.rbegin(); it != controls.rend(); ++it) for (auto it = controls.rbegin(); it != controls.rend(); ++it)
@@ -124,6 +139,7 @@ bool Canvas::handleEvent(const ExMessage& msg)
bool cConsumed = c->handleEvent(msg); bool cConsumed = c->handleEvent(msg);
if (c->isDirty()) anyDirty = true; if (c->isDirty()) anyDirty = true;
if (c->didEventAffectVisual()) anyVisualChanged = true;
if (cConsumed) if (cConsumed)
{ {
@@ -135,8 +151,9 @@ bool Canvas::handleEvent(const ExMessage& msg)
if (firstConsumer && !SxIsNoisyMsg(msg.message)) if (firstConsumer && !SxIsNoisyMsg(msg.message))
{ {
SX_LOGD("Event") << SX_T("Canvas 消耗消息: ","Canvas consumed: msg=") << msg.message SX_LOGD("Event") << SX_T("Canvas 消耗消息: ","Canvas consumed: ")
<< SX_T("子控件"," by child")<<" id=" << firstConsumer->getId(); << SxCanvasMsgName(msg.message)
<< SX_T(" 子控件 id=", " childId=") << firstConsumer->getId();
} }
if (anyDirty) if (anyDirty)
@@ -145,6 +162,7 @@ bool Canvas::handleEvent(const ExMessage& msg)
SX_LOGD("Dirty") << SX_T("Canvas检测有控件为脏状态 -> 请求重绘, ","Canvas anyDirty -> requestRepaint, ")<<"id = " << id; SX_LOGD("Dirty") << SX_T("Canvas检测有控件为脏状态 -> 请求重绘, ","Canvas anyDirty -> requestRepaint, ")<<"id = " << id;
requestRepaint(parent); requestRepaint(parent);
} }
markEventVisualChanged(anyVisualChanged);
return consumed; return consumed;
} }
+4
View File
@@ -41,6 +41,7 @@ protected:
Control* parent = nullptr; // 父控件 Control* parent = nullptr; // 父控件
bool dirty = true; // 是否重绘 bool dirty = true; // 是否重绘
bool show = true; // 是否显示 bool show = true; // 是否显示
bool eventVisualChanged = false; // 最近一次 handleEvent 是否真的引发了视觉变化
/* == 布局模式 == */ /* == 布局模式 == */
StellarX::LayoutMode layoutMode = StellarX::LayoutMode::Fixed; // 布局模式 StellarX::LayoutMode layoutMode = StellarX::LayoutMode::Fixed; // 布局模式
@@ -128,6 +129,7 @@ public:
std::string getId() const { return id; } std::string getId() const { return id; }
//检查是否为脏 //检查是否为脏
bool isDirty() { return dirty; } bool isDirty() { return dirty; }
bool didEventAffectVisual() const { return eventVisualChanged; }
//用来检查对话框是否模态,其他控件不用实现 //用来检查对话框是否模态,其他控件不用实现
virtual bool model()const = 0; virtual bool model()const = 0;
//布局 //布局
@@ -139,4 +141,6 @@ public:
protected: protected:
void saveStyle(); void saveStyle();
void restoreStyle(); void restoreStyle();
void resetEventVisualChanged() { eventVisualChanged = false; }
void markEventVisualChanged(bool changed = true) { eventVisualChanged = changed; }
}; };
+53 -25
View File
@@ -44,12 +44,15 @@ void Dialog::draw()
//绘制消息文本 //绘制消息文本
settextcolor(textStyle.color); settextcolor(textStyle.color);
setbkmode(TRANSPARENT);
//设置字体样式 //设置字体样式
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace, settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight, textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
outtextxy(x + 5, y + 5, LPCTSTR(titleText.c_str()));
int ty = y + closeButtonHeight + titleToTextMargin; // 文本起始Y坐标 int ty = y + closeButtonHeight + titleToTextMargin; // 文本起始Y坐标
for (auto& line : lines) for (auto& line : lines)
{ {
@@ -68,6 +71,7 @@ void Dialog::draw()
bool Dialog::handleEvent(const ExMessage& msg) bool Dialog::handleEvent(const ExMessage& msg)
{ {
bool consume = false; bool consume = false;
resetEventVisualChanged();
if (!show) if (!show)
{ {
if (pendingCleanup && !isCleaning) if (pendingCleanup && !isCleaning)
@@ -80,12 +84,20 @@ bool Dialog::handleEvent(const ExMessage& msg)
// 如果正在清理或标记为待清理,则不处理事件 // 如果正在清理或标记为待清理,则不处理事件
if (pendingCleanup || isCleaning) if (pendingCleanup || isCleaning)
return false; return false;
// 模态对话框不允许点击外部区域
// 模态对话框:点击对话框外部区域时,发出提示音(\a)并吞噬该事件,不允许操作背景内容。 const bool isMouseMessage =
if (modal && msg.message == WM_LBUTTONUP && msg.message == WM_MOUSEMOVE ||
(msg.x < x || msg.x > x + width || msg.y < y || msg.y > y + height)) msg.message == WM_LBUTTONDOWN ||
msg.message == WM_LBUTTONUP;
const bool insideDialog =
msg.x >= x && msg.x <= x + width &&
msg.y >= y && msg.y <= y + height;
// 模态对话框区域外鼠标事件不允许落到底层背景。
if (modal && isMouseMessage && !insideDialog)
{ {
std::cout << "\a" << std::endl; if (msg.message == WM_LBUTTONUP)
std::cout << "\a" << std::endl;
return true; return true;
} }
@@ -93,6 +105,10 @@ bool Dialog::handleEvent(const ExMessage& msg)
if (!consume) if (!consume)
consume = Canvas::handleEvent(msg); consume = Canvas::handleEvent(msg);
// 对话框矩形范围内的鼠标事件一律由对话框吞掉,避免穿透到底层控件。
if (isMouseMessage && insideDialog)
consume = true;
// 每次事件处理后检查是否需要执行延迟清理 // 每次事件处理后检查是否需要执行延迟清理
if (pendingCleanup && !isCleaning) if (pendingCleanup && !isCleaning)
performDelayedCleanup(); performDelayedCleanup();
@@ -182,9 +198,8 @@ void Dialog::Show()
// 立即统一收口:父窗重绘 背景+普通控件(不会画到这只模态) // 立即统一收口:父窗重绘 背景+普通控件(不会画到这只模态)
hWnd.pumpResizeIfNeeded(); hWnd.pumpResizeIfNeeded();
// 这只模态在新尺寸下重建布局 / 重抓背景 → 本帧要画自己 // 这只模态只重新居中,不参与拉伸;背景快照需要在新位置重抓。
setInitialization(true); recenterInHostWindow();
setDirty(true);
} }
// ② 处理这只对话框的鼠标/键盘(沿用原来 EX_MOUSE | EX_KEY // ② 处理这只对话框的鼠标/键盘(沿用原来 EX_MOUSE | EX_KEY
@@ -257,10 +272,30 @@ void Dialog::setInitialization(bool init)
} }
} }
void Dialog::recenterInHostWindow()
{
if (!show)
return;
// 尚未完成首次初始化时,保持“延迟初始化”语义,由首次 draw 统一创建子控件。
if (needsInitialization || width <= 0 || height <= 0)
{
dirty = true;
return;
}
const int newX = (hWnd.getWidth() - width) / 2;
const int newY = (hWnd.getHeight() - height) / 2;
invalidateBackgroundSnapshot();
x = newX;
y = newY;
rebuildChrome();
dirty = true;
}
void Dialog::initButtons() void Dialog::initButtons()
{ {
controls.clear();
switch (this->type) switch (this->type)
{ {
case StellarX::MessageBoxType::OK: // 只有确定按钮 case StellarX::MessageBoxType::OK: // 只有确定按钮
@@ -489,16 +524,6 @@ void Dialog::initCloseButton()
this->addControl(std::move(but)); this->addControl(std::move(but));
} }
void Dialog::initTitle()
{
auto titleLabel = std::make_unique<Label>(this->x + 5, this->y + 5, titleText, textStyle.color);
titleLabel->setTextdisap(true);
titleLabel->textStyle = this->textStyle;
this->title = titleLabel.get();
this->addControl(std::move(titleLabel));
}
void Dialog::splitMessageLines() void Dialog::splitMessageLines()
{ {
lines.clear(); // 先清空现有的行 lines.clear(); // 先清空现有的行
@@ -570,6 +595,13 @@ void Dialog::invalidateLayout(bool clearChildren)
this->dirty = true; this->dirty = true;
} }
void Dialog::rebuildChrome()
{
clearControls();
initButtons();
initCloseButton();
}
// 计算逻辑:对话框宽度取【文本区域最大宽度】和【按钮区域总宽度】中的较大值。 // 计算逻辑:对话框宽度取【文本区域最大宽度】和【按钮区域总宽度】中的较大值。
// 对话框高度 = 标题栏 + 文本区 + 按钮区 + 各种间距。 // 对话框高度 = 标题栏 + 文本区 + 按钮区 + 各种间距。
void Dialog::initDialogSize() void Dialog::initDialogSize()
@@ -626,9 +658,7 @@ void Dialog::initDialogSize()
this->x = (hWnd.getWidth() - this->width) / 2; this->x = (hWnd.getWidth() - this->width) / 2;
this->y = (hWnd.getHeight() - this->height) / 2; this->y = (hWnd.getHeight() - this->height) / 2;
initButtons(); // 初始化按钮 rebuildChrome();
initTitle(); // 初始化标题标签
initCloseButton(); // 初始化关闭按钮
} }
void Dialog::addControl(std::unique_ptr<Control> control) void Dialog::addControl(std::unique_ptr<Control> control)
@@ -655,7 +685,6 @@ void Dialog::performDelayedCleanup()
// 重置指针 // 重置指针
closeButton = nullptr; closeButton = nullptr;
title = nullptr;
// 释放背景图像资源 // 释放背景图像资源
if (saveBkImage && hasSnap) if (saveBkImage && hasSnap)
{ {
@@ -709,7 +738,6 @@ void Dialog::clearControls()
controls.clear(); controls.clear();
// 重置按钮指针 // 重置按钮指针
closeButton = nullptr; closeButton = nullptr;
title = nullptr; // 释放标题资源
} }
std::unique_ptr<Button> Dialog::createDialogButton(int x, int y, const std::string& text) std::unique_ptr<Button> Dialog::createDialogButton(int x, int y, const std::string& text)
+4 -3
View File
@@ -40,7 +40,6 @@ class Dialog : public Canvas
StellarX::MessageBoxType type = StellarX::MessageBoxType::OK; //对话框类型 StellarX::MessageBoxType type = StellarX::MessageBoxType::OK; //对话框类型
std::string titleText = "提示"; //标题文本 std::string titleText = "提示"; //标题文本
Label* title = nullptr; //标题标签(由 controls 持有)
std::string message; //提示信息 std::string message; //提示信息
std::vector<std::string> lines; //消息内容按行分割 std::vector<std::string> lines; //消息内容按行分割
@@ -105,14 +104,14 @@ public:
void Close(); void Close();
//初始化 //初始化
void setInitialization(bool init); void setInitialization(bool init);
// 宿主窗口变化时仅重新居中,不拉伸 Dialog 自身
void recenterInHostWindow();
private: private:
// 初始化按钮 // 初始化按钮
void initButtons(); void initButtons();
// 初始化关闭按钮 // 初始化关闭按钮
void initCloseButton(); void initCloseButton();
// 初始化标题
void initTitle();
// 按行分割消息内容 // 按行分割消息内容
void splitMessageLines(); void splitMessageLines();
// 获取文本大小 // 获取文本大小
@@ -121,6 +120,8 @@ private:
void invalidateLayout(bool clearChildren); void invalidateLayout(bool clearChildren);
//初始化对话框尺寸 //初始化对话框尺寸
void initDialogSize(); void initDialogSize();
// 依据当前 Dialog 的 x/y/width/height 重新创建标题和按钮
void rebuildChrome();
void addControl(std::unique_ptr<Control> control); void addControl(std::unique_ptr<Control> control);
// 清除所有控件 // 清除所有控件
+2
View File
@@ -99,6 +99,7 @@ void TextBox::draw()
bool TextBox::handleEvent(const ExMessage& msg) bool TextBox::handleEvent(const ExMessage& msg)
{ {
if (!show) return false; if (!show) return false;
resetEventVisualChanged();
bool hover = false; bool hover = false;
bool oldClick = click; bool oldClick = click;
@@ -165,6 +166,7 @@ bool TextBox::handleEvent(const ExMessage& msg)
if (dirty) if (dirty)
requestRepaint(parent); requestRepaint(parent);
markEventVisualChanged(dirty);
if (click) if (click)
click = false; click = false;
+35 -14
View File
@@ -3,6 +3,8 @@
#include"SxLog.h" #include"SxLog.h"
#include <easyx.h> #include <easyx.h>
#include <algorithm> #include <algorithm>
// 可能频繁出现且对调试信息干扰较大的消息(例如鼠标移动),
// 可以在日志输出时特殊处理以减少干扰。
static bool SxIsNoisyMsg(UINT m) static bool SxIsNoisyMsg(UINT m)
{ {
return m == WM_MOUSEMOVE; return m == WM_MOUSEMOVE;
@@ -425,6 +427,17 @@ int Window::runEventLoop()
continue; continue;
} }
bool hasVisibleNonModalDialog = false;
for (const auto& d : dialogs)
{
if (d->IsVisible() && !d->model())
{
hasVisibleNonModalDialog = true;
break;
}
}
bool controlVisualChanged = false;
// 输入优先:先给顶层“非模态对话框”,再传给普通控件 // 输入优先:先给顶层“非模态对话框”,再传给普通控件
for (auto it = dialogs.rbegin(); it != dialogs.rend(); ++it) for (auto it = dialogs.rbegin(); it != dialogs.rend(); ++it)
{ {
@@ -433,7 +446,9 @@ int Window::runEventLoop()
consume = d->handleEvent(msg); consume = d->handleEvent(msg);
if (consume) if (consume)
{ {
SX_LOGD("Event") << SX_T("事件被非模态对话框处理","Event consumed by non-modal dialog"); if (!SxIsNoisyMsg(msg.message))
SX_LOGD("Event") << SX_T("事件被非模态对话框处理:", "Event consumed by non-modal dialog: ")
<< SxMsgName(msg.message);
break; break;
} }
} }
@@ -441,15 +456,21 @@ int Window::runEventLoop()
{ {
for (auto it = controls.rbegin(); it != controls.rend(); ++it) for (auto it = controls.rbegin(); it != controls.rend(); ++it)
{ {
consume = (*it)->handleEvent(msg); Control* current = it->get();
consume = current->handleEvent(msg);
if (current->didEventAffectVisual())
controlVisualChanged = true;
if (consume) if (consume)
{ {
SX_LOGD("Event") << SX_T("事件被控件处理 id=", "Event consumed by control id=") << (*it)->getId(); if (!SxIsNoisyMsg(msg.message))
redrawDialogs = true; // 控件事件可能引起对话框状态变化,标记需要重绘对话框 SX_LOGD("Event") << SX_T("事件被控件处理:", "Event consumed by control: ")
<< SxMsgName(msg.message)
<< SX_T(" id=", " id=") << current->getId();
break; break;
} }
} }
if (hasVisibleNonModalDialog && controlVisualChanged)
redrawDialogs = true; // 只有普通控件真的发生视觉变化时,才补画非模态对话框
} }
} }
// 关键点⑦:事件处理后,如果对话框状态可能变化(例如控件事件引起的控件重绘可能导致对话框被覆盖),优先重绘对话框以确保界面响应及时;后续再根据 needResizeDirty 进行一次性重绘。 // 关键点⑦:事件处理后,如果对话框状态可能变化(例如控件事件引起的控件重绘可能导致对话框被覆盖),优先重绘对话框以确保界面响应及时;后续再根据 needResizeDirty 进行一次性重绘。
@@ -550,14 +571,6 @@ int Window::runEventLoop()
// 批量通知控件“窗口尺寸变化”,并标记重绘 // 批量通知控件“窗口尺寸变化”,并标记重绘
for (auto& c : controls) for (auto& c : controls)
adaptiveLayout(c, finalH, finalW); adaptiveLayout(c, finalH, finalW);
for (auto& d : dialogs)
{
if (auto dd = dynamic_cast<Dialog*>(d.get()))
{
dd->setDirty(true);
dd->setInitialization(true);
}
}
//重绘窗口 //重绘窗口
Resize(nullptr, finalW, finalH); Resize(nullptr, finalW, finalH);
@@ -585,6 +598,14 @@ int Window::runEventLoop()
// 最终提交“当前已应用尺寸”(用于外部查询/下次比较) // 最终提交“当前已应用尺寸”(用于外部查询/下次比较)
width = renderWidth; width = renderWidth;
height = renderHeight; height = renderHeight;
for (auto& d : dialogs)
{
if (auto dd = dynamic_cast<Dialog*>(d.get()))
{
dd->recenterInHostWindow();
}
}
} }
// 统一批量绘制 // 统一批量绘制
@@ -782,7 +803,7 @@ void Window::pumpResizeIfNeeded()
} }
for (auto& d : dialogs) for (auto& d : dialogs)
if (auto* dd = dynamic_cast<Dialog*>(d.get())) if (auto* dd = dynamic_cast<Dialog*>(d.get()))
dd->setInitialization(true); // 强制对话框在新尺寸下重建布局/快照 dd->recenterInHostWindow(); // 窗口变化时仅重新居中,不拉伸 Dialog 自身
// 重绘 // 重绘
for (auto& c : controls) c->draw(); for (auto& c : controls) c->draw();
+13 -1
View File
@@ -195,11 +195,22 @@ int main()
os.clear(); os.clear();
} }
} }
selectionAreaLabel->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::Left);
selectionAreaLabel->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
selectionArea->addControl(std::move(selectionAreaLabel)); selectionArea->addControl(std::move(selectionAreaLabel));
for (auto& s : selectionAreaButton) for (auto& s : selectionAreaButton)
{
s->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::Left);
s->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
selectionArea->addControl(std::move(s)); selectionArea->addControl(std::move(s));
}
for (auto& s : selectionAreaButtonLabel) for (auto& s : selectionAreaButtonLabel)
{
s->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::Left);
s->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
selectionArea->addControl(std::move(s)); selectionArea->addControl(std::move(s));
}
//功能区控件 //功能区控件
//功能区总容器 //功能区总容器
auto function = std::make_unique<Canvas>(10, 170, 680, 70); auto function = std::make_unique<Canvas>(10, 170, 680, 70);
@@ -536,7 +547,7 @@ int main()
signedTogglePtr->setOnToggleOnListener([&]() { signedTogglePtr->setOnToggleOnListener([&]() {
gSigned = true; gSigned = true;
signedTogglePtr->setButtonText("有符号"); signedTogglePtr->setButtonText("有符号");
StellarX::MessageBox::showAsync(mainWindow, "有符号模式下,\n最高位为符号位,\n其余位为数值位。", "有符号模式"); StellarX::MessageBox::showModal(mainWindow, "有符号模式下,\n最高位为符号位,\n其余位为数值位。", "有符号模式");
// 立即刷新十进制显示:用当前位图算出新值,仅改 dec // 立即刷新十进制显示:用当前位图算出新值,仅改 dec
auto cur = snapshotBits(); auto cur = snapshotBits();
const uint32_t u = [&] { uint32_t v = 0; for (int b = 0; b < 32; ++b) if (cur[b]) v |= (1u << b); return v; }(); const uint32_t u = [&] { uint32_t v = 0; for (int b = 0; b < 32; ++b) if (cur[b]) v |= (1u << b); return v; }();
@@ -562,6 +573,7 @@ int main()
selectionArea->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::Left); selectionArea->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::Left);
selectionArea->setLayoutMode(StellarX::LayoutMode::AnchorToEdges); selectionArea->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
mainWindow.addControl(std::move(selectionArea)); mainWindow.addControl(std::move(selectionArea));
mainWindow.addControl(std::move(function)); mainWindow.addControl(std::move(function));
mainWindow.addControl(std::move(NumericalDisplayArea)); mainWindow.addControl(std::move(NumericalDisplayArea));