363 lines
11 KiB
C++
363 lines
11 KiB
C++
#include "circular_menu.h"
|
||
|
||
#include <QtCore/QPointer>
|
||
#include <QtGui/QKeyEvent>
|
||
#include <QtGui/QPainter>
|
||
#include <QtGui/QMouseEvent>
|
||
#include <QtGui/QIcon>
|
||
#include <cmath>
|
||
|
||
#include "application_config.h"
|
||
|
||
//==============================================================================
|
||
class CircularMenuData
|
||
{
|
||
public:
|
||
qsizetype activeIndex {-1};
|
||
int radius {210};
|
||
int buttonRadius {54};
|
||
int iconSize {40};
|
||
QList<QPointer<QAction>> actions;
|
||
QList<QPointer<QAction>> 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<QAction>(action) );
|
||
d->updateVisibleActions();
|
||
update();
|
||
}
|
||
}
|
||
//==============================================================================
|
||
void CircularMenu::addActions(QList<QAction *> actions)
|
||
{
|
||
for (auto action: actions) {
|
||
|
||
if (action) {
|
||
|
||
d->actions.append( QPointer<QAction>(action) );
|
||
}
|
||
}
|
||
|
||
d->updateVisibleActions();
|
||
update();
|
||
}
|
||
//==============================================================================
|
||
qsizetype CircularMenu::count() const
|
||
{
|
||
return d->actions.size();
|
||
}
|
||
//==============================================================================
|
||
QList<QAction*> CircularMenu::actions() const
|
||
{
|
||
QList<QAction*> 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<int>(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<QMouseEvent*>(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<double>(i) / static_cast<double>(count) - M_PI / 2.0;
|
||
QPoint buttonCenter(
|
||
static_cast<int>(center.x() + d->radius * cos(angle)),
|
||
static_cast<int>(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<int>(newActiveIndex));
|
||
#endif
|
||
|
||
if (action && action->isEnabled()) {
|
||
|
||
d->activeIndex = newActiveIndex;
|
||
update();
|
||
}
|
||
}
|
||
}
|
||
else if ((event->type() == QMouseEvent::MouseButtonPress) && isVisible()) {
|
||
|
||
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(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<int>(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<QKeyEvent*>(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<double>(i) / static_cast<double>(count) - M_PI / 2.0;
|
||
int x = static_cast<int>(center.x() + d->radius * cos(angle) - d->buttonRadius);
|
||
int y = static_cast<int>(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);
|
||
}
|
||
//==============================================================================
|