#include "circular_menu.h" #include #include #include #include #include #include #include "application_config.h" //============================================================================== class CircularMenuData { public: qsizetype activeIndex {-1}; int radius {210}; int buttonRadius {54}; int iconSize {40}; QList> actions; QList> visibleActions; Qt::Key key {Qt::Key_Alt}; // CircularMenuData() = default; virtual ~CircularMenuData() = default; void updateVisibleActions() { visibleActions.clear(); for (auto i = 0; i < actions.size(); ++i) { if (actions.at(i) && actions.at(i)->isVisible() && !actions.at(i)->isCheckable()) { visibleActions.append(actions.at(i)); } } }; }; //============================================================================== CircularMenu::CircularMenu(QWidget *parent) : QWidget{parent} , d{new CircularMenuData} { setWindowFlags(windowFlags() | Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint ); setAttribute(Qt::WA_TranslucentBackground); setWindowModality(Qt::WindowModal); setMouseTracking(true); setFixedSize(40 + 2 * (d->radius + d->buttonRadius), 40 + 2 * (d->radius + d->buttonRadius)); if (parent) { parent->installEventFilter(this); } this->installEventFilter(this); } //============================================================================== CircularMenu::~CircularMenu() { delete d; } //============================================================================== void CircularMenu::addAction(QAction *action) { if (action) { d->actions.append( QPointer(action) ); d->updateVisibleActions(); update(); } } //============================================================================== void CircularMenu::addActions(QList actions) { for (auto action: actions) { if (action) { d->actions.append( QPointer(action) ); } } d->updateVisibleActions(); update(); } //============================================================================== qsizetype CircularMenu::count() const { return d->actions.size(); } //============================================================================== QList CircularMenu::actions() const { QList actions; for (auto i = 0; i < d->actions.size(); ++i) { if (d->actions.at(i)) actions.append(d->actions.at(i).data()); } return actions; } //============================================================================== QAction *CircularMenu::actionAt(qsizetype index) const { if (index < 0) return nullptr; if (index >= d->actions.size()) return nullptr; return #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) d->actions.at(index).data(); #else d->actions.at(static_cast(index)).data(); #endif } //============================================================================== bool CircularMenu::removeAction(qsizetype index, qsizetype count) { if (index < 0) return false; if (index >= d->actions.size()) return false; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) d->actions.remove(index, count); #else d->actions.erase(d->actions.begin() + index, d->actions.begin() + index + count); #endif d->updateVisibleActions(); update(); return true; } //============================================================================== void CircularMenu::clear() { d->actions.clear(); d->updateVisibleActions(); } //============================================================================== void CircularMenu::setHotKey(Qt::Key key) { d->key = key; } //============================================================================== bool CircularMenu::eventFilter(QObject *obj, QEvent *event) { if (!obj || !event) return false; if ((obj != parent()) && (obj != this)) return false; if ((event->type() == QMouseEvent::MouseMove) && isVisible()) { QMouseEvent *mouseEvent = static_cast(event); if (!mouseEvent) return false; const QPoint center = rect().center(); const QPoint mousePos = mouseEvent->pos(); qsizetype newActiveIndex = -1; const auto count = d->visibleActions.size(); for (auto i = 0; i < count; ++i) { if (!d->visibleActions.at(i) || !d->visibleActions.at(i)->isEnabled()) continue; // Вычисляем позицию кнопки double angle = 2.0 * M_PI * static_cast(i) / static_cast(count) - M_PI / 2.0; QPoint buttonCenter( static_cast(center.x() + d->radius * cos(angle)), static_cast(center.y() + d->radius * sin(angle)) ); // Проверяем попадание курсора в круглую область кнопки QPoint diff = mousePos - buttonCenter; double distanceToButton = sqrt(diff.x()*diff.x() + diff.y()*diff.y()); if (distanceToButton <= (d->buttonRadius + 10)) { newActiveIndex = i; break; // Курсор может быть только над одной кнопкой } } if ((newActiveIndex != d->activeIndex) && (newActiveIndex != -1) && (newActiveIndex < d->visibleActions.size())) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QAction *action = d->visibleActions.at(newActiveIndex); #else QAction *action = d->visibleActions.at(static_cast(newActiveIndex)); #endif if (action && action->isEnabled()) { d->activeIndex = newActiveIndex; update(); } } } else if ((event->type() == QMouseEvent::MouseButtonPress) && isVisible()) { QMouseEvent *mouseEvent = static_cast(event); if (!mouseEvent) return false; if (mouseEvent->button() == Qt::LeftButton && (d->activeIndex != -1) && (d->activeIndex < d->visibleActions.size())) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QAction *action = d->visibleActions.at(d->activeIndex); #else QAction *action = d->visibleActions.at(static_cast(d->activeIndex)); #endif this->hide(); if (action && action->isEnabled()) action->trigger(); return true; } } else if ((event->type() == QKeyEvent::KeyRelease) && isVisible()) { this->hide(); return false; } else if (event->type() == QKeyEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if (!keyEvent) return false; if (keyEvent->key() != d->key) { this->hide(); return false; } d->updateVisibleActions(); d->activeIndex = d->visibleActions.isEmpty() ? -1 : 0; Application::moveToCenterMainWindow(this); this->show(); return true; } return false; } //============================================================================== void CircularMenu::paintEvent(QPaintEvent *event) { Q_UNUSED(event) QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); // Рисуем круглый фон painter.setBrush(QBrush(QColor(0, 0, 0, 50))); painter.setPen(QPen(QColor(255, 255, 255, 60), 4)); painter.drawEllipse(rect()); // Draw menu circle painter.setPen(Qt::NoPen); painter.setBrush(QColor(0, 5, 10, 130)); painter.drawEllipse(rect().center(), d->radius - d->buttonRadius / 2 - 30, d->radius - d->buttonRadius / 2 - 30); // Draw buttons const auto count = d->visibleActions.size(); const QPoint center = rect().center(); for (auto i = 0; i < count; ++i) { if (!d->visibleActions.at(i)) continue; // Calculate button position double angle = 2.0 * M_PI * static_cast(i) / static_cast(count) - M_PI / 2.0; int x = static_cast(center.x() + d->radius * cos(angle) - d->buttonRadius); int y = static_cast(center.y() + d->radius * sin(angle) - d->buttonRadius); // Draw button if (i == d->activeIndex) { painter.setBrush(QColor(0, 15, 50, 200)); painter.setPen(QPen(QColor(0, 180, 255, 200), 5)); } else { painter.setBrush(QColor(0, 5, 10, 200)); painter.setPen(QPen(QColor(255, 255, 255, 180), 3)); } painter.drawEllipse(QPoint(x + d->buttonRadius, y + d->buttonRadius), d->buttonRadius, d->buttonRadius); if (!d->visibleActions.at(i)->icon().isNull()) { QRect iconRect(x + d->buttonRadius - d->iconSize / 2, y + d->buttonRadius - d->iconSize / 2, d->iconSize, d->iconSize); if (d->visibleActions.at(i)->isEnabled()) { d->visibleActions.at(i)->icon().paint(&painter, iconRect); } else { painter.save(); painter.setOpacity(0.4); // Make icon semi-transparent QIcon::Mode mode = QIcon::Disabled; QIcon::State state = d->visibleActions.at(i)->isChecked() ? QIcon::On : QIcon::Off; d->visibleActions.at(i)->icon().paint(&painter, iconRect, Qt::AlignCenter, mode, state); painter.restore(); } } if (i == d->activeIndex) { QRect r(0, 0, d->radius * 2 - 20, d->radius * 2 - 20); r.moveCenter(rect().center()); QFont font {painter.font()}; font.setPointSize(14); painter.setFont(font); painter.setBrush(Qt::NoBrush); painter.setPen(Qt::white); painter.drawText(r, Qt::AlignCenter, d->visibleActions.at(i)->text()); } } } //============================================================================== void CircularMenu::resizeEvent(QResizeEvent *event) { Q_UNUSED(event); QRegion region(rect(), QRegion::Ellipse); setMask(region); } //==============================================================================