432 lines
13 KiB
C++
432 lines
13 KiB
C++
#include "stdafx.h"
|
|
/**
|
|
* @file TetrisLayout.cpp
|
|
* @brief 实现窗口缩放布局和各类按钮、卡片、列表项的点击区域计算。
|
|
*/
|
|
|
|
#include "TetrisAppInternal.h"
|
|
|
|
/**
|
|
* @brief 将指定滚动偏移按步长调整,并限制在非负范围内。
|
|
* @param scrollOffset 需要修改的滚动偏移。
|
|
* @param delta 本次滚动增量。
|
|
*/
|
|
void AdjustScrollOffset(int& scrollOffset, int delta)
|
|
{
|
|
// 先应用本次滚动增量,再统一夹紧到允许范围内。
|
|
scrollOffset += delta;
|
|
if (scrollOffset < 0)
|
|
{
|
|
scrollOffset = 0;
|
|
}
|
|
if (scrollOffset > 2400)
|
|
{
|
|
scrollOffset = 2400;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief 按当前窗口缩放返回一次滚动操作的像素距离。
|
|
* @param hWnd 当前窗口句柄。
|
|
* @param baseStep 设计稿中的基础滚动步长。
|
|
* @return 缩放后的滚动步长。
|
|
*/
|
|
int GetScrollStep(HWND hWnd, int baseStep)
|
|
{
|
|
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
|
|
return MulDiv(baseStep, metrics.scale, 1000);
|
|
}
|
|
|
|
/**
|
|
* @brief 根据当前窗口大小计算整体界面缩放与偏移。
|
|
* @param hWnd 当前窗口句柄。
|
|
* @return 布局缩放、偏移和网格尺寸。
|
|
*/
|
|
LayoutMetrics GetLayoutMetrics(HWND hWnd)
|
|
{
|
|
RECT clientRect;
|
|
GetClientRect(hWnd, &clientRect);
|
|
|
|
// 以设计稿窗口为基准计算缩放,取较小比例保证完整界面不被裁切。
|
|
int clientWidth = clientRect.right - clientRect.left;
|
|
int clientHeight = clientRect.bottom - clientRect.top;
|
|
int scaleX = MulDiv(clientWidth, 1000, WINDOW_CLIENT_WIDTH);
|
|
int scaleY = MulDiv(clientHeight, 1000, WINDOW_CLIENT_HEIGHT);
|
|
int scale = (scaleX < scaleY) ? scaleX : scaleY;
|
|
if (scale < 500)
|
|
{
|
|
scale = 500;
|
|
}
|
|
|
|
LayoutMetrics metrics = {};
|
|
metrics.scale = scale;
|
|
metrics.layoutWidth = MulDiv(WINDOW_CLIENT_WIDTH, scale, 1000);
|
|
metrics.layoutHeight = MulDiv(WINDOW_CLIENT_HEIGHT, scale, 1000);
|
|
// 横向居中显示,纵向从顶部开始,方便窗口高度不足时保持棋盘起点稳定。
|
|
metrics.offsetX = (clientWidth - metrics.layoutWidth) / 2;
|
|
metrics.offsetY = 0;
|
|
metrics.grid = MulDiv(GRID, scale, 1000);
|
|
return metrics;
|
|
}
|
|
|
|
/**
|
|
* @brief 按当前布局比例缩放一个尺寸值。
|
|
* @param metrics 当前布局参数。
|
|
* @param value 设计稿尺寸值。
|
|
* @return 缩放后的像素尺寸。
|
|
*/
|
|
int ScaleValue(const LayoutMetrics& metrics, int value)
|
|
{
|
|
return MulDiv(value, metrics.scale, 1000);
|
|
}
|
|
|
|
/**
|
|
* @brief 按当前布局比例缩放横坐标并叠加窗口偏移。
|
|
* @param metrics 当前布局参数。
|
|
* @param value 设计稿横坐标。
|
|
* @return 实际窗口横坐标。
|
|
*/
|
|
int ScaleXValue(const LayoutMetrics& metrics, int value)
|
|
{
|
|
return metrics.offsetX + MulDiv(value, metrics.scale, 1000);
|
|
}
|
|
|
|
/**
|
|
* @brief 按当前布局比例缩放纵坐标并叠加窗口偏移。
|
|
* @param metrics 当前布局参数。
|
|
* @param value 设计稿纵坐标。
|
|
* @return 实际窗口纵坐标。
|
|
*/
|
|
int ScaleYValue(const LayoutMetrics& metrics, int value)
|
|
{
|
|
return metrics.offsetY + MulDiv(value, metrics.scale, 1000);
|
|
}
|
|
|
|
/**
|
|
* @brief 获取主菜单中央卡片区域。
|
|
* @param hWnd 当前窗口句柄。
|
|
* @return 菜单卡片在窗口中的矩形区域。
|
|
*/
|
|
static RECT GetMenuCardRect(HWND hWnd)
|
|
{
|
|
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
|
|
RECT rect =
|
|
{
|
|
ScaleXValue(metrics, 110),
|
|
ScaleYValue(metrics, 70),
|
|
ScaleXValue(metrics, WINDOW_CLIENT_WIDTH - 110),
|
|
ScaleYValue(metrics, WINDOW_CLIENT_HEIGHT - 70)
|
|
};
|
|
return rect;
|
|
}
|
|
|
|
/**
|
|
* @brief 获取主菜单选项的点击区域。
|
|
* @param hWnd 当前窗口句柄。
|
|
* @param index 菜单选项序号。
|
|
* @return 选项在窗口中的矩形区域。
|
|
*/
|
|
RECT GetMenuOptionRect(HWND hWnd, int index)
|
|
{
|
|
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
|
|
RECT menuCard = GetMenuCardRect(hWnd);
|
|
int top = menuCard.top + ScaleValue(metrics, 140) + index * ScaleValue(metrics, 130);
|
|
RECT rect =
|
|
{
|
|
menuCard.left + ScaleValue(metrics, 36),
|
|
top,
|
|
menuCard.right - ScaleValue(metrics, 36),
|
|
top + ScaleValue(metrics, 104)
|
|
};
|
|
return rect;
|
|
}
|
|
|
|
/**
|
|
* @brief 获取帮助页卡片区域。
|
|
* @param hWnd 当前窗口句柄。
|
|
* @return 帮助卡片在窗口中的矩形区域。
|
|
*/
|
|
static RECT GetRulesCardRect(HWND hWnd)
|
|
{
|
|
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
|
|
RECT rect =
|
|
{
|
|
ScaleXValue(metrics, 76),
|
|
ScaleYValue(metrics, 54),
|
|
ScaleXValue(metrics, WINDOW_CLIENT_WIDTH - 76),
|
|
ScaleYValue(metrics, WINDOW_CLIENT_HEIGHT - 54)
|
|
};
|
|
return rect;
|
|
}
|
|
|
|
/**
|
|
* @brief 获取帮助页首页选项的点击区域。
|
|
* @param hWnd 当前窗口句柄。
|
|
* @param index 帮助选项序号。
|
|
* @return 选项在窗口中的矩形区域。
|
|
*/
|
|
RECT GetHelpOptionRect(HWND hWnd, int index)
|
|
{
|
|
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
|
|
RECT rulesCard = GetRulesCardRect(hWnd);
|
|
RECT contentRect =
|
|
{
|
|
rulesCard.left + ScaleValue(metrics, 36),
|
|
rulesCard.top + ScaleValue(metrics, 126),
|
|
rulesCard.right - ScaleValue(metrics, 36),
|
|
rulesCard.bottom - ScaleValue(metrics, 86)
|
|
};
|
|
int optionHeight = ScaleValue(metrics, 100);
|
|
int optionGap = ScaleValue(metrics, 22);
|
|
int optionTop = contentRect.top + ScaleValue(metrics, 18);
|
|
RECT rect =
|
|
{
|
|
contentRect.left,
|
|
optionTop + index * (optionHeight + optionGap),
|
|
contentRect.right,
|
|
optionTop + index * (optionHeight + optionGap) + optionHeight
|
|
};
|
|
return rect;
|
|
}
|
|
|
|
/**
|
|
* @brief 获取技能演示列表项的点击区域。
|
|
* @param hWnd 当前窗口句柄。
|
|
* @param index 技能演示条目序号。
|
|
* @return 条目在窗口中的矩形区域。
|
|
*/
|
|
RECT GetHelpSkillDemoItemRect(HWND hWnd, int index)
|
|
{
|
|
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
|
|
RECT rulesCard = GetRulesCardRect(hWnd);
|
|
RECT contentRect =
|
|
{
|
|
rulesCard.left + ScaleValue(metrics, 36),
|
|
rulesCard.top + ScaleValue(metrics, 126),
|
|
rulesCard.right - ScaleValue(metrics, 36),
|
|
rulesCard.bottom - ScaleValue(metrics, 86)
|
|
};
|
|
int itemHeight = ScaleValue(metrics, 58);
|
|
int itemGap = ScaleValue(metrics, 10);
|
|
int itemTop = contentRect.top + ScaleValue(metrics, 8) - helpScrollOffset;
|
|
RECT rect =
|
|
{
|
|
contentRect.left,
|
|
itemTop + index * (itemHeight + itemGap),
|
|
contentRect.right,
|
|
itemTop + index * (itemHeight + itemGap) + itemHeight
|
|
};
|
|
return rect;
|
|
}
|
|
|
|
/**
|
|
* @brief 获取帮助页底部返回提示的点击区域。
|
|
* @param hWnd 当前窗口句柄。
|
|
* @return 返回提示在窗口中的矩形区域。
|
|
*/
|
|
RECT GetHelpBackHintRect(HWND hWnd)
|
|
{
|
|
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
|
|
RECT rulesCard = GetRulesCardRect(hWnd);
|
|
RECT rect =
|
|
{
|
|
rulesCard.left + ScaleValue(metrics, 36),
|
|
rulesCard.bottom - ScaleValue(metrics, 58),
|
|
rulesCard.right - ScaleValue(metrics, 36),
|
|
rulesCard.bottom - ScaleValue(metrics, 24)
|
|
};
|
|
return rect;
|
|
}
|
|
|
|
/**
|
|
* @brief 获取致谢页左右箭头按钮区域。
|
|
* @param hWnd 当前窗口句柄。
|
|
* @param direction 小于 0 为左箭头,大于 0 为右箭头。
|
|
* @return 箭头按钮在窗口中的矩形区域。
|
|
*/
|
|
RECT GetCreditArrowRect(HWND hWnd, int direction)
|
|
{
|
|
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
|
|
RECT rulesCard = GetRulesCardRect(hWnd);
|
|
int size = ScaleValue(metrics, 54);
|
|
int centerY = (rulesCard.top + rulesCard.bottom) / 2;
|
|
int left = direction < 0
|
|
? rulesCard.left + ScaleValue(metrics, 52)
|
|
: rulesCard.right - ScaleValue(metrics, 52) - size;
|
|
|
|
RECT rect =
|
|
{
|
|
left,
|
|
centerY - size / 2,
|
|
left + size,
|
|
centerY + size / 2
|
|
};
|
|
return rect;
|
|
}
|
|
|
|
/**
|
|
* @brief 获取升级选择覆盖层区域。
|
|
* @param hWnd 当前窗口句柄。
|
|
* @return 覆盖层在窗口中的矩形区域。
|
|
*/
|
|
static RECT GetUpgradeOverlayRect(HWND hWnd)
|
|
{
|
|
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
|
|
RECT rect =
|
|
{
|
|
ScaleXValue(metrics, 60),
|
|
ScaleYValue(metrics, 80),
|
|
ScaleXValue(metrics, WINDOW_CLIENT_WIDTH - 60),
|
|
ScaleYValue(metrics, WINDOW_CLIENT_HEIGHT - 80)
|
|
};
|
|
return rect;
|
|
}
|
|
|
|
/**
|
|
* @brief 获取升级选择卡片的点击区域。
|
|
* @param hWnd 当前窗口句柄。
|
|
* @param index 强化卡片序号。
|
|
* @return 卡片在窗口中的矩形区域。
|
|
*/
|
|
RECT GetUpgradeCardRect(HWND hWnd, int index)
|
|
{
|
|
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
|
|
RECT overlayRect = GetUpgradeOverlayRect(hWnd);
|
|
|
|
// 根据当前候选数量自动决定列数;最多三列,两行用于命运轮盘六选项。
|
|
int gap = ScaleValue(metrics, 18);
|
|
int horizontalPadding = ScaleValue(metrics, 36);
|
|
int verticalTop = overlayRect.top + ScaleValue(metrics, 138);
|
|
int columnCount = upgradeUiState.optionCount <= 3 ? upgradeUiState.optionCount : 3;
|
|
if (columnCount < 1)
|
|
{
|
|
columnCount = 1;
|
|
}
|
|
int rowCount = (upgradeUiState.optionCount + columnCount - 1) / columnCount;
|
|
if (rowCount < 1)
|
|
{
|
|
rowCount = 1;
|
|
}
|
|
int cardWidth = (overlayRect.right - overlayRect.left - horizontalPadding * 2 - gap * (columnCount - 1)) / columnCount;
|
|
int availableHeight = overlayRect.bottom - verticalTop - ScaleValue(metrics, 72) - (rowCount - 1) * gap;
|
|
int cardHeight = availableHeight / rowCount;
|
|
int column = index % columnCount;
|
|
int row = index / columnCount;
|
|
int left = overlayRect.left + horizontalPadding + column * (cardWidth + gap);
|
|
int top = verticalTop + row * (cardHeight + gap);
|
|
RECT rect = { left, top, left + cardWidth, top + cardHeight };
|
|
return rect;
|
|
}
|
|
|
|
/**
|
|
* @brief 获取暂停或结束提示覆盖层区域。
|
|
* @param hWnd 当前窗口句柄。
|
|
* @return 覆盖层在窗口中的矩形区域。
|
|
*/
|
|
static RECT GetGameOverlayRect(HWND hWnd)
|
|
{
|
|
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
|
|
int panelGap = ScaleValue(metrics, SIDE_PANEL_GAP);
|
|
int panelWidth = ScaleValue(metrics, SIDE_PANEL_WIDTH);
|
|
int boardLeft = ScaleXValue(metrics, WINDOW_PADDING) + panelWidth + panelGap;
|
|
int boardTop = ScaleYValue(metrics, WINDOW_PADDING);
|
|
int boardWidth = nGameWidth * metrics.grid;
|
|
RECT rect =
|
|
{
|
|
boardLeft + ScaleValue(metrics, 28),
|
|
boardTop + metrics.grid * 6 + ScaleValue(metrics, 10),
|
|
boardLeft + boardWidth - ScaleValue(metrics, 28),
|
|
boardTop + metrics.grid * 10 + ScaleValue(metrics, 30)
|
|
};
|
|
return rect;
|
|
}
|
|
|
|
/**
|
|
* @brief 获取暂停或结束覆盖层按钮的点击区域。
|
|
* @param hWnd 当前窗口句柄。
|
|
* @param index 按钮序号。
|
|
* @param buttonCount 当前覆盖层按钮总数。
|
|
* @return 按钮在窗口中的矩形区域。
|
|
*/
|
|
RECT GetOverlayButtonRect(HWND hWnd, int index, int buttonCount)
|
|
{
|
|
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
|
|
RECT overlayRect = GetGameOverlayRect(hWnd);
|
|
|
|
// 游戏结束可能有三个按钮,暂停只有两个按钮,因此间距和边距分开计算。
|
|
int gap = buttonCount == 3 ? ScaleValue(metrics, 8) : ScaleValue(metrics, 18);
|
|
int sidePadding = buttonCount == 3 ? ScaleValue(metrics, 14) : ScaleValue(metrics, 34);
|
|
int width = (overlayRect.right - overlayRect.left - sidePadding * 2 - gap * (buttonCount - 1)) / buttonCount;
|
|
int height = ScaleValue(metrics, 44);
|
|
int left = overlayRect.left + sidePadding + index * (width + gap);
|
|
int top = overlayRect.top + ScaleValue(metrics, 94);
|
|
RECT rect = { left, top, left + width, top + height };
|
|
return rect;
|
|
}
|
|
|
|
/**
|
|
* @brief 获取左上角返回按钮的点击区域。
|
|
* @param hWnd 当前窗口句柄。
|
|
* @return 返回按钮在窗口中的矩形区域。
|
|
*/
|
|
RECT GetBackButtonRect(HWND hWnd)
|
|
{
|
|
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
|
|
RECT rect =
|
|
{
|
|
ScaleXValue(metrics, 6),
|
|
ScaleYValue(metrics, 6),
|
|
ScaleXValue(metrics, 34),
|
|
ScaleYValue(metrics, 34)
|
|
};
|
|
return rect;
|
|
}
|
|
|
|
/**
|
|
* @brief 获取右下角音乐按钮的点击区域。
|
|
* @param hWnd 当前窗口句柄。
|
|
* @return 音乐按钮在窗口中的矩形区域。
|
|
*/
|
|
RECT GetMusicButtonRect(HWND hWnd)
|
|
{
|
|
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
|
|
|
|
// 音乐按钮保持最小可点击尺寸,避免窗口缩小时变得难以点中。
|
|
int size = ScaleValue(metrics, 28);
|
|
if (size < 22)
|
|
{
|
|
size = 22;
|
|
}
|
|
int marginRight = ScaleValue(metrics, 12);
|
|
if (marginRight < 6)
|
|
{
|
|
marginRight = 6;
|
|
}
|
|
int marginBottom = ScaleValue(metrics, 12);
|
|
if (marginBottom < 6)
|
|
{
|
|
marginBottom = 6;
|
|
}
|
|
|
|
RECT buttonRect =
|
|
{
|
|
metrics.offsetX + metrics.layoutWidth - marginRight - size,
|
|
metrics.offsetY + metrics.layoutHeight - marginBottom - size,
|
|
metrics.offsetX + metrics.layoutWidth - marginRight,
|
|
metrics.offsetY + metrics.layoutHeight - marginBottom
|
|
};
|
|
return buttonRect;
|
|
}
|
|
|
|
/**
|
|
* @brief 判断点坐标是否落在矩形内部。
|
|
* @param rect 待判断矩形。
|
|
* @param x 点的横坐标。
|
|
* @param y 点的纵坐标。
|
|
* @return 点在矩形内返回 true,否则返回 false。
|
|
*/
|
|
bool IsPointInRect(const RECT& rect, int x, int y)
|
|
{
|
|
return x >= rect.left && x < rect.right && y >= rect.top && y < rect.bottom;
|
|
}
|