#include #include "FocusWindow.h" using namespace FW; /** * @brief 判断某个控件是否为某个方向上的候选控件 * @param focused 当前聚焦的控件的全局位置 * @param focusable 当前可以拥有焦点的控件全局位置 * @param direction 方向 * @return */ bool FocusWindow::isCandidate(QRect focused, QRect focusable, Direction direction) { switch (direction) { case Up: return (focused.bottom() > focusable.bottom() || focused.top() >= focusable.bottom()) && focused.top() > focusable.top(); case Down: return (focused.top() < focusable.top() || focused.bottom() <= focusable.top()) && focused.bottom() < focusable.bottom(); case Left: return (focused.right() > focused.right() || focused.left() >= focusable.right()) && focused.left() > focusable.left(); case Right: return (focused.left() < focusable.left() || focused.right() <= focusable.left()) && focused.right() < focusable.right(); default: return false; } } /** * @brief 判断rect1与rect2在direction方向是否重叠 * @param direction 方向 * @param rect1 第一个矩形 * @param rect2 第二个矩形 * @return */ bool FocusWindow::beamsOverlap(Direction direction, QRect rect1, QRect rect2) { switch (direction) { case Left: case Right: return (rect2.bottom() >= rect1.top()) && (rect2.top() <= rect1.bottom()); case Up: case Down: return (rect2.right() >= rect1.left()) && (rect2.left() <= rect1.right()); } } /** * @brief 判断dest是否在source的direction方向上 * @param direction * @param source * @param dest * @return */ bool FocusWindow::isToDirectionOf(Direction direction, QRect source, QRect dest) { switch (direction) { case Up: return source.top() >= dest.bottom(); case Down: return source.bottom() <= dest.top(); case Left: return source.left() >= dest.right(); case Right: return source.right() <= dest.left(); } } /** * @brief 计算主轴方向上的距离 * @param direction * @param source * @param dest * @return */ int FocusWindow::majorAxisDistance(Direction direction, QRect source, QRect dest) { return qMax(0, majorAxisDistanceRaw(direction, source, dest)); } /** * @brief 计算主轴方向的距离 * @param direction * @param source * @param dest * @return */ int FocusWindow::majorAxisDistanceRaw(Direction direction, QRect source, QRect dest) { switch (direction) { case Up: return source.top() - dest.bottom(); case Down: return dest.top() - source.bottom(); case Left: return source.left() - dest.right(); case Right: return dest.left() - source.right(); default: return 0; } } /** * @brief 主轴方向边缘距离 * @param dircetion * @param source * @param dest * @return */ int FocusWindow::majorAxisDistanceToFarEdge(Direction dircetion, QRect source, QRect dest) { return qMax(1, majorAxisDistanceToFarEdgeRaw(dircetion, source, dest)); } /** * @brief 主轴方向边缘距离 * @param dircetion * @param source * @param dest * @return */ int FocusWindow::majorAxisDistanceToFarEdgeRaw(Direction dircetion, QRect source, QRect dest) { switch (dircetion) { case Up: return source.top() - dest.top(); case Down: return dest.bottom() - source.bottom(); case Left: return source.left() - dest.left(); case Right: return dest.right() - source.right(); default: return 0; } } /** * @brief 计算副轴方向上的距离(中心点的距离) * @param direction * @param source * @param dest * @return */ int FocusWindow::minorAxisDistance(Direction direction, QRect source, QRect dest) { switch (direction) { case Up: case Down: return qAbs((source.left() + source.width() / 2) - (dest.left() + dest.width() / 2)); case Left: case Right: return qAbs((source.top() + source.height() / 2) - (dest.top() + dest.height() / 2)); default: return 0; } } /** * @brief 计算综合距离, 计算公式 13 * major^2 + minor ^ 2(13为权重) * @param direction * @param majorAxisDistance 主轴方向的距离 * @param minorAxisDistance 副轴方向的距离 * @return */ int FocusWindow::getWeightDistanceFor(int majorAxisDistance, int minorAxisDistance) { return 13 * majorAxisDistance * majorAxisDistance + minorAxisDistance * minorAxisDistance; } /** * @brief 判断rect1相比与rect2相对于source在dircetion方向上是否更优 * @param dircetion 方向 * @param source 参照物 * @param rect1 第一个矩形 * @param rect2 第二个矩形 * @return */ bool FocusWindow::beamBeats(Direction direction, QRect source, QRect rect1, QRect rect2) { bool rect1InSrcBeam = beamsOverlap(direction, source, rect1); bool rect2InSrcBeam = beamsOverlap(direction, source, rect2); // rect2与参照物有重叠,rect1无重叠 if (rect2InSrcBeam || !rect1InSrcBeam) { return false; } // rect1有重叠,rect2无重叠 // ↓ // rect2是否在direction方向上 if (!isToDirectionOf(direction, source, rect2)) { return true; } // 水平方向上一定更优 if (direction == Left || direction == Right) { return true; } return (majorAxisDistance(direction, source, rect1) < majorAxisDistanceToFarEdge(direction, source, rect2)); } /** * @brief 判断是否是更好的候选项 * @param direction 方向 * @param focused 当前聚焦的控件的全局位置 * @param focusable 当前聚焦的控件的全局位置 * @param curCandidate 当前最佳候选的全局位置 * @return */ bool FocusWindow::isBetterCandidate(Direction direction, QRect focused, QRect focusable, QRect curCandidate) { // 当前的控件不符合条件(条件是在direction方向) if (!isCandidate(focused, focusable, direction)) { return false; } // 候选控件不符合条件 if (!isCandidate(focused, curCandidate, direction)) return true; // 当前的控件符合条件,将它与原来的候选控件进行对比 if (beamBeats(direction, focused, focusable, curCandidate)) { return true; } // 将当前候选控件与当前控件进行对比 if (beamBeats(direction, focused, curCandidate, focusable)) { return false; } // 计算当前控件到聚焦控件的综合距离是否小于候选控件到聚焦控件的综合距离 return (getWeightDistanceFor(majorAxisDistance(direction, focused, focusable), minorAxisDistance(direction, focused, focusable)) < getWeightDistanceFor(majorAxisDistance(direction, focused, curCandidate), minorAxisDistance(direction, focused, curCandidate))); } /** * @brief 查找指定方向上最近的一个控件 * @param direction * @return */ QWidget* FocusWindow::getNextFocusWidget(Direction direction) { focusableList = getAllFocusabelWidget(); QWidget* nextFocus = nullptr; // 候选列表 QList candidates; QWidget* focusedWidget = QApplication::focusWidget(); if (!focusedWidget) return nextFocus; // 计算当前聚焦控件的全局位置 QRect focusedRect = QRect(focusedWidget->mapToGlobal(QPoint(0, 0)), focusedWidget->size()); // 虚构一个候选,即原本的位置向相反方向移动一像素 QRect betterCandidateRect = focusedRect; switch (direction) { case Up: betterCandidateRect = QRect(QPoint(focusedRect.x(), focusedRect.y() + 1), focusedRect.size()); break; case Down: betterCandidateRect = QRect(QPoint(focusedRect.x(), focusedRect.y() - 1), focusedRect.size()); break; case Left: betterCandidateRect = QRect(QPoint(focusedRect.x() + 1, focusedRect.y()), focusedRect.size()); break; case Right: betterCandidateRect = QRect(QPoint(focusedRect.x() - 1, focusedRect.y()), focusedRect.size()); break; default: break; } // 循环遍历比较控件位置,找到所有候选控件 for (int i = 0; i < focusableList.length(); i++) { QWidget* focusable = focusableList.at(i); // 如果可聚焦控件是当前获得焦点的控件则跳转比较 if (focusable == focusedWidget) continue; // 将可聚焦控件的位置转换为全局的位置 QRect focusableRect = QRect(focusable->mapToGlobal(QPoint(0, 0)), focusable->size()); // 计算是否为更优后选 if (isBetterCandidate(direction, focusedRect, focusableRect, betterCandidateRect)) { betterCandidateRect = focusableRect; nextFocus = focusable; } } return nextFocus; } /** * @brief 聚焦下一个控件 * @param direction */ void FocusWindow::focusNext(Direction direction) { QWidget* nextFocus = getNextFocusWidget(direction); if (nextFocus) { nextFocus->setFocus(); } }