Qt_Circular_Menu_Example/lib_circular_menu/private/circular_menu.cpp

363 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#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);
}
//==============================================================================