diff --git a/src/editor/dialogs/addeventdialog.cpp b/src/editor/dialogs/addeventdialog.cpp index 0734da2..d088f09 100644 --- a/src/editor/dialogs/addeventdialog.cpp +++ b/src/editor/dialogs/addeventdialog.cpp @@ -82,6 +82,78 @@ AddEventDialog::AddEventDialog(ProjectTreeModel &projectModel, MainWindow &mainW m_ui->pushButtonCollision->setMenu(menu); } + { + auto menu = new QMenu; + menu->addAction(tr("Left button"), this, [this](){ m_eventType = Object::EventType::MouseLeftButton; accept(); }); + menu->addAction(tr("Right button"), this, [this](){ m_eventType = Object::EventType::MouseRightButton; accept(); }); + menu->addAction(tr("Middle button"), this, [this](){ m_eventType = Object::EventType::MouseMiddleButton; accept(); }); + menu->addSeparator(); + menu->addAction(tr("No button"), this, [this](){ m_eventType = Object::EventType::MouseNoButton; accept(); }); + menu->addSeparator(); + menu->addAction(tr("Left pressed"), this, [this](){ m_eventType = Object::EventType::MouseLeftPressed; accept(); }); + menu->addAction(tr("Right pressed"), this, [this](){ m_eventType = Object::EventType::MouseRightPressed; accept(); }); + menu->addAction(tr("Middle pressed"), this, [this](){ m_eventType = Object::EventType::MouseMiddlePressed; accept(); }); + menu->addSeparator(); + menu->addAction(tr("Left released"), this, [this](){ m_eventType = Object::EventType::MouseLeftReleased; accept(); }); + menu->addAction(tr("Right released"), this, [this](){ m_eventType = Object::EventType::MouseRightReleased; accept(); }); + menu->addAction(tr("Middle released"), this, [this](){ m_eventType = Object::EventType::MouseMiddleReleased; accept(); }); + menu->addSeparator(); + menu->addAction(tr("Mouse enter"), this, [this](){ m_eventType = Object::EventType::MouseEnter; accept(); }); + menu->addAction(tr("Mouse leave"), this, [this](){ m_eventType = Object::EventType::MouseLeave; accept(); }); + menu->addSeparator(); + menu->addAction(tr("Mouse wheel up"), this, [this](){ m_eventType = Object::EventType::MouseWheelUp; accept(); }); + menu->addAction(tr("Mouse wheel down"), this, [this](){ m_eventType = Object::EventType::MouseWheelDown; accept(); }); + menu->addSeparator(); + { + auto subMenu = menu->addMenu(tr("Global mouse")); + subMenu->addAction(tr("Global Left button"), this, [this](){ m_eventType = Object::EventType::MouseGlobalLeftButton; accept(); }); + subMenu->addAction(tr("Global Right button"), this, [this](){ m_eventType = Object::EventType::MouseGlobalRightButton; accept(); }); + subMenu->addAction(tr("Global Middle button"), this, [this](){ m_eventType = Object::EventType::MouseGlobalMiddleButton; accept(); }); + subMenu->addSeparator(); + subMenu->addAction(tr("Global Left pressed"), this, [this](){ m_eventType = Object::EventType::MouseGlobalLeftPressed; accept(); }); + subMenu->addAction(tr("Global Right pressed"), this, [this](){ m_eventType = Object::EventType::MouseGlobalRightPressed; accept(); }); + subMenu->addAction(tr("Global Middle pressed"), this, [this](){ m_eventType = Object::EventType::MouseGlobalMiddlePressed; accept(); }); + subMenu->addSeparator(); + subMenu->addAction(tr("Global Left released"), this, [this](){ m_eventType = Object::EventType::MouseGlobalLeftReleased; accept(); }); + subMenu->addAction(tr("Global Right released"), this, [this](){ m_eventType = Object::EventType::MouseGlobalRightReleased; accept(); }); + subMenu->addAction(tr("Global Middle released"), this, [this](){ m_eventType = Object::EventType::MouseGlobalMiddleReleased; accept(); }); + } + menu->addSeparator(); + { + auto subMenu = menu->addMenu(tr("Joystick 1")); + subMenu->addAction(tr("Left"), this, [this](){ m_eventType = Object::EventType::MouseJoystick1Left; accept(); }); + subMenu->addAction(tr("Right"), this, [this](){ m_eventType = Object::EventType::MouseJoystick1Right; accept(); }); + subMenu->addAction(tr("Up"), this, [this](){ m_eventType = Object::EventType::MouseJoystick1Up; accept(); }); + subMenu->addAction(tr("Down"), this, [this](){ m_eventType = Object::EventType::MouseJoystick1Down; accept(); }); + subMenu->addSeparator(); + subMenu->addAction(tr("Button 1"), this, [this](){ m_eventType = Object::EventType::MouseJoystick1Button1; accept(); }); + subMenu->addAction(tr("Button 2"), this, [this](){ m_eventType = Object::EventType::MouseJoystick1Button2; accept(); }); + subMenu->addAction(tr("Button 3"), this, [this](){ m_eventType = Object::EventType::MouseJoystick1Button3; accept(); }); + subMenu->addAction(tr("Button 4"), this, [this](){ m_eventType = Object::EventType::MouseJoystick1Button4; accept(); }); + subMenu->addAction(tr("Button 5"), this, [this](){ m_eventType = Object::EventType::MouseJoystick1Button5; accept(); }); + subMenu->addAction(tr("Button 6"), this, [this](){ m_eventType = Object::EventType::MouseJoystick1Button6; accept(); }); + subMenu->addAction(tr("Button 7"), this, [this](){ m_eventType = Object::EventType::MouseJoystick1Button7; accept(); }); + subMenu->addAction(tr("Button 8"), this, [this](){ m_eventType = Object::EventType::MouseJoystick1Button8; accept(); }); + } + { + auto subMenu = menu->addMenu(tr("Joystick 2")); + subMenu->addAction(tr("Left"), this, [this](){ m_eventType = Object::EventType::MouseJoystick2Left; accept(); }); + subMenu->addAction(tr("Right"), this, [this](){ m_eventType = Object::EventType::MouseJoystick2Right; accept(); }); + subMenu->addAction(tr("Up"), this, [this](){ m_eventType = Object::EventType::MouseJoystick2Up; accept(); }); + subMenu->addAction(tr("Down"), this, [this](){ m_eventType = Object::EventType::MouseJoystick2Down; accept(); }); + subMenu->addSeparator(); + subMenu->addAction(tr("Button 1"), this, [this](){ m_eventType = Object::EventType::MouseJoystick2Button1; accept(); }); + subMenu->addAction(tr("Button 2"), this, [this](){ m_eventType = Object::EventType::MouseJoystick2Button2; accept(); }); + subMenu->addAction(tr("Button 3"), this, [this](){ m_eventType = Object::EventType::MouseJoystick2Button3; accept(); }); + subMenu->addAction(tr("Button 4"), this, [this](){ m_eventType = Object::EventType::MouseJoystick2Button4; accept(); }); + subMenu->addAction(tr("Button 5"), this, [this](){ m_eventType = Object::EventType::MouseJoystick2Button5; accept(); }); + subMenu->addAction(tr("Button 6"), this, [this](){ m_eventType = Object::EventType::MouseJoystick2Button6; accept(); }); + subMenu->addAction(tr("Button 7"), this, [this](){ m_eventType = Object::EventType::MouseJoystick2Button7; accept(); }); + subMenu->addAction(tr("Button 8"), this, [this](){ m_eventType = Object::EventType::MouseJoystick2Button8; accept(); }); + } + m_ui->pushButtonMouse->setMenu(menu); + } + { auto menu = new QMenu; menu->addAction(tr("Add/Modify Triggers..."), this, [this](){ m_mainWindow.showDefineTriggers(); reject(); }); diff --git a/src/editor/dialogs/addeventdialog.ui b/src/editor/dialogs/addeventdialog.ui index beb6733..fa16330 100644 --- a/src/editor/dialogs/addeventdialog.ui +++ b/src/editor/dialogs/addeventdialog.ui @@ -32,6 +32,10 @@ &Mouse + + + :/qtgameengine/icons/event-mouse.png:/qtgameengine/icons/event-mouse.png + @@ -133,7 +137,7 @@ - QDialogButtonBox::Cancel + QDialogButtonBox::StandardButton::Cancel true diff --git a/src/editor/icons/event-mouse.png b/src/editor/icons/event-mouse.png new file mode 100644 index 0000000..3c2b676 Binary files /dev/null and b/src/editor/icons/event-mouse.png differ diff --git a/src/editor/models/objecteventsmodel.cpp b/src/editor/models/objecteventsmodel.cpp index c7f7984..4d52205 100644 --- a/src/editor/models/objecteventsmodel.cpp +++ b/src/editor/models/objecteventsmodel.cpp @@ -38,24 +38,71 @@ QVariant ObjectEventsModel::data(const QModelIndex &index, int role) const case Qt::EditRole: switch (pair.first) { - case Object::EventType::Create: return tr("Create"); - case Object::EventType::Destroy: return tr("Destroy"); - case Object::EventType::Alarm0: return tr("Alarm 0"); - case Object::EventType::Alarm1: return tr("Alarm 1"); - case Object::EventType::Alarm2: return tr("Alarm 2"); - case Object::EventType::Alarm3: return tr("Alarm 3"); - case Object::EventType::Alarm4: return tr("Alarm 4"); - case Object::EventType::Alarm5: return tr("Alarm 5"); - case Object::EventType::Alarm6: return tr("Alarm 6"); - case Object::EventType::Alarm7: return tr("Alarm 7"); - case Object::EventType::Alarm8: return tr("Alarm 8"); - case Object::EventType::Alarm9: return tr("Alarm 9"); - case Object::EventType::Alarm10: return tr("Alarm 10"); - case Object::EventType::Alarm11: return tr("Alarm 11"); - case Object::EventType::Step: return tr("Step"); - case Object::EventType::BeginStep: return tr("Begin Step"); - case Object::EventType::EndStep: return tr("End Step"); - case Object::EventType::Draw: return tr("Draw"); + case Object::EventType::Create: return tr("Create"); + case Object::EventType::Destroy: return tr("Destroy"); + case Object::EventType::Alarm0: return tr("Alarm 0"); + case Object::EventType::Alarm1: return tr("Alarm 1"); + case Object::EventType::Alarm2: return tr("Alarm 2"); + case Object::EventType::Alarm3: return tr("Alarm 3"); + case Object::EventType::Alarm4: return tr("Alarm 4"); + case Object::EventType::Alarm5: return tr("Alarm 5"); + case Object::EventType::Alarm6: return tr("Alarm 6"); + case Object::EventType::Alarm7: return tr("Alarm 7"); + case Object::EventType::Alarm8: return tr("Alarm 8"); + case Object::EventType::Alarm9: return tr("Alarm 9"); + case Object::EventType::Alarm10: return tr("Alarm 10"); + case Object::EventType::Alarm11: return tr("Alarm 11"); + case Object::EventType::Step: return tr("Step"); + case Object::EventType::BeginStep: return tr("Begin Step"); + case Object::EventType::EndStep: return tr("End Step"); + case Object::EventType::Draw: return tr("Draw"); + case Object::EventType::MouseLeftButton: return tr("Left button"); + case Object::EventType::MouseRightButton: return tr("Right Button"); + case Object::EventType::MouseMiddleButton: return tr("Middle Button"); + case Object::EventType::MouseNoButton: return tr("No Button"); + case Object::EventType::MouseLeftPressed: return tr("Left Pressed"); + case Object::EventType::MouseRightPressed: return tr("Right Pressed"); + case Object::EventType::MouseMiddlePressed: return tr("Middle Pressed"); + case Object::EventType::MouseLeftReleased: return tr("Left Released"); + case Object::EventType::MouseRightReleased: return tr("Right Released"); + case Object::EventType::MouseMiddleReleased: return tr("Middle Released"); + case Object::EventType::MouseEnter: return tr("Enter"); + case Object::EventType::MouseLeave: return tr("Leave"); + case Object::EventType::MouseWheelUp: return tr("Wheel Up"); + case Object::EventType::MouseWheelDown: return tr("Wheel Down"); + case Object::EventType::MouseGlobalLeftButton: return tr("Global Left Button"); + case Object::EventType::MouseGlobalRightButton: return tr("Global Right Button"); + case Object::EventType::MouseGlobalMiddleButton: return tr("Global Middle Button"); + case Object::EventType::MouseGlobalLeftPressed: return tr("Global Left Pressed"); + case Object::EventType::MouseGlobalRightPressed: return tr("Global Right Pressed"); + case Object::EventType::MouseGlobalMiddlePressed: return tr("Global Middle Pressed"); + case Object::EventType::MouseGlobalLeftReleased: return tr("Global Left Released"); + case Object::EventType::MouseGlobalRightReleased: return tr("Global Right Released"); + case Object::EventType::MouseGlobalMiddleReleased: return tr("Global Middle Released"); + case Object::EventType::MouseJoystick1Left: return tr("Joystick 1 Left"); + case Object::EventType::MouseJoystick1Right: return tr("Joystick 1 Right"); + case Object::EventType::MouseJoystick1Up: return tr("Joystick 1 Up"); + case Object::EventType::MouseJoystick1Down: return tr("Joystick 1 Down"); + case Object::EventType::MouseJoystick1Button1: return tr("Joystick 1 Button 1"); + case Object::EventType::MouseJoystick1Button2: return tr("Joystick 1 Button 2"); + case Object::EventType::MouseJoystick1Button3: return tr("Joystick 1 Button 3"); + case Object::EventType::MouseJoystick1Button4: return tr("Joystick 1 Button 4"); + case Object::EventType::MouseJoystick1Button5: return tr("Joystick 1 Button 5"); + case Object::EventType::MouseJoystick1Button6: return tr("Joystick 1 Button 6"); + case Object::EventType::MouseJoystick1Button7: return tr("Joystick 1 Button 7"); + case Object::EventType::MouseJoystick1Button8: return tr("Joystick 1 Button 8"); + case Object::EventType::MouseJoystick2Left: return tr("Joystick 2 Left"); + case Object::EventType::MouseJoystick2Right: return tr("Joystick 2 Right"); + case Object::EventType::MouseJoystick2Up: return tr("Joystick 2 Up"); + case Object::EventType::MouseJoystick2Down: return tr("Joystick 2 Down"); + case Object::EventType::MouseJoystick2Button1: return tr("Joystick 2 Button 1"); + case Object::EventType::MouseJoystick2Button2: return tr("Joystick 2 Button 2"); + case Object::EventType::MouseJoystick2Button3: return tr("Joystick 2 Button 3"); + case Object::EventType::MouseJoystick2Button4: return tr("Joystick 2 Button 4"); + case Object::EventType::MouseJoystick2Button5: return tr("Joystick 2 Button 5"); + case Object::EventType::MouseJoystick2Button6: return tr("Joystick 2 Button 6"); + case Object::EventType::MouseJoystick2Button7: return tr("Joystick 2 Button 7"); + case Object::EventType::MouseJoystick2Button8: return tr("Joystick 2 Button 8"); } qWarning() << "unknown event type" << std::to_underlying(pair.first); @@ -63,8 +110,8 @@ QVariant ObjectEventsModel::data(const QModelIndex &index, int role) const case Qt::DecorationRole: switch (pair.first) { - case Object::EventType::Create: return QIcon{":/qtgameengine/icons/event-create.png"}; - case Object::EventType::Destroy: return QIcon{":/qtgameengine/icons/event-destroy.png"}; + case Object::EventType::Create: return QIcon{":/qtgameengine/icons/event-create.png"}; + case Object::EventType::Destroy: return QIcon{":/qtgameengine/icons/event-destroy.png"}; case Object::EventType::Alarm0: case Object::EventType::Alarm1: case Object::EventType::Alarm2: @@ -76,11 +123,58 @@ QVariant ObjectEventsModel::data(const QModelIndex &index, int role) const case Object::EventType::Alarm8: case Object::EventType::Alarm9: case Object::EventType::Alarm10: - case Object::EventType::Alarm11: return QIcon{":/qtgameengine/icons/event-alarm.png"}; + case Object::EventType::Alarm11: return QIcon{":/qtgameengine/icons/event-alarm.png"}; case Object::EventType::Step: case Object::EventType::BeginStep: - case Object::EventType::EndStep: return QIcon{":/qtgameengine/icons/event-step.png"}; - case Object::EventType::Draw: return QIcon{":/qtgameengine/icons/event-draw.png"}; + case Object::EventType::EndStep: return QIcon{":/qtgameengine/icons/event-step.png"}; + case Object::EventType::Draw: return QIcon{":/qtgameengine/icons/event-draw.png"}; + case Object::EventType::MouseLeftButton: + case Object::EventType::MouseRightButton: + case Object::EventType::MouseMiddleButton: + case Object::EventType::MouseNoButton: + case Object::EventType::MouseLeftPressed: + case Object::EventType::MouseRightPressed: + case Object::EventType::MouseMiddlePressed: + case Object::EventType::MouseLeftReleased: + case Object::EventType::MouseRightReleased: + case Object::EventType::MouseMiddleReleased: + case Object::EventType::MouseEnter: + case Object::EventType::MouseLeave: + case Object::EventType::MouseWheelUp: + case Object::EventType::MouseWheelDown: + case Object::EventType::MouseGlobalLeftButton: + case Object::EventType::MouseGlobalRightButton: + case Object::EventType::MouseGlobalMiddleButton: + case Object::EventType::MouseGlobalLeftPressed: + case Object::EventType::MouseGlobalRightPressed: + case Object::EventType::MouseGlobalMiddlePressed: + case Object::EventType::MouseGlobalLeftReleased: + case Object::EventType::MouseGlobalRightReleased: + case Object::EventType::MouseGlobalMiddleReleased: + case Object::EventType::MouseJoystick1Left: + case Object::EventType::MouseJoystick1Right: + case Object::EventType::MouseJoystick1Up: + case Object::EventType::MouseJoystick1Down: + case Object::EventType::MouseJoystick1Button1: + case Object::EventType::MouseJoystick1Button2: + case Object::EventType::MouseJoystick1Button3: + case Object::EventType::MouseJoystick1Button4: + case Object::EventType::MouseJoystick1Button5: + case Object::EventType::MouseJoystick1Button6: + case Object::EventType::MouseJoystick1Button7: + case Object::EventType::MouseJoystick1Button8: + case Object::EventType::MouseJoystick2Left: + case Object::EventType::MouseJoystick2Right: + case Object::EventType::MouseJoystick2Up: + case Object::EventType::MouseJoystick2Down: + case Object::EventType::MouseJoystick2Button1: + case Object::EventType::MouseJoystick2Button2: + case Object::EventType::MouseJoystick2Button3: + case Object::EventType::MouseJoystick2Button4: + case Object::EventType::MouseJoystick2Button5: + case Object::EventType::MouseJoystick2Button6: + case Object::EventType::MouseJoystick2Button7: + case Object::EventType::MouseJoystick2Button8: return QIcon{":/qtgameengine/icons/event-mouse.png"}; } qWarning() << "unknown event type" << std::to_underlying(pair.first); diff --git a/src/editor/resources_editor.qrc b/src/editor/resources_editor.qrc index 7964bc1..5af7df6 100644 --- a/src/editor/resources_editor.qrc +++ b/src/editor/resources_editor.qrc @@ -47,6 +47,7 @@ icons/event-create.png icons/event-destroy.png icons/event-draw.png + icons/event-mouse.png icons/event-step.png icons/exit.png icons/export-resources.png diff --git a/src/engine/gameengine.cpp b/src/engine/gameengine.cpp index 54b4cea..778db73 100644 --- a/src/engine/gameengine.cpp +++ b/src/engine/gameengine.cpp @@ -3,6 +3,9 @@ #include #include +#include +#include +#include #include "closeeventfilter.h" #include "projectcontainer.h" @@ -22,6 +25,8 @@ GameEngine::GameEngine(const ProjectContainer &project, QObject *parent) : m_glGameWindow.setModality(Qt::WindowModal); m_vulkanGameWindow.setModality(Qt::WindowModal); + m_glGameWindow.installEventFilter(this); + m_vulkanGameWindow.installEventFilter(this); m_frameTimer.setTimerType(Qt::PreciseTimer); m_frameTimer.setSingleShot(true); @@ -41,6 +46,81 @@ GameEngine::GameEngine(const ProjectContainer &project, QObject *parent) : }); } +bool GameEngine::eventFilter(QObject *watched, QEvent *event) +{ + if (watched != &m_glGameWindow && watched != &m_vulkanGameWindow) + return QObject::eventFilter(watched, event); + + auto *window = static_cast(watched); + const auto roomPosition = [this, window](QPointF position) { + return QPointF{position.x() * m_scene.roomSize.width() / std::max(1, window->width()), + position.y() * m_scene.roomSize.height() / std::max(1, window->height())}; + }; + + switch (event->type()) + { + case QEvent::FocusIn: + if (m_mouseSource != window) + { + m_scene.resetMouseInput(); + m_mouseSource = window; + } + break; + case QEvent::FocusOut: + case QEvent::Hide: + if (m_mouseSource == window) + { + m_scene.resetMouseInput(); + m_mouseSource = nullptr; + } + break; + case QEvent::Enter: + if (!m_mouseSource) + m_mouseSource = window; + if (m_mouseSource == window) + m_scene.queueMouseMove(roomPosition(static_cast(event)->position())); + break; + case QEvent::Leave: + if (m_mouseSource == window) + m_scene.queueMouseLeave(); + break; + case QEvent::MouseMove: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + { + auto *mouse = static_cast(event); + if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick) + { + if (m_mouseSource != window) + { + m_scene.resetMouseInput(); + m_mouseSource = window; + } + } + if (m_mouseSource != window) + break; + const QPointF position = roomPosition(mouse->position()); + if (event->type() == QEvent::MouseMove) + m_scene.queueMouseMove(position); + else + m_scene.queueMouseButton(position, mouse->button(), + event->type() != QEvent::MouseButtonRelease); + break; + } + case QEvent::Wheel: + if (m_mouseSource == window) + { + auto *wheel = static_cast(event); + m_scene.queueMouseWheel(roomPosition(wheel->position()), wheel->angleDelta().y()); + } + break; + default: + break; + } + return QObject::eventFilter(watched, event); +} + void GameEngine::run() { if (m_project.rooms.empty()) diff --git a/src/engine/gameengine.h b/src/engine/gameengine.h index eae0efa..da06c90 100644 --- a/src/engine/gameengine.h +++ b/src/engine/gameengine.h @@ -21,6 +21,9 @@ public: explicit GameEngine(const ProjectContainer &project, QObject *parent = nullptr); void run(); +protected: + bool eventFilter(QObject *watched, QEvent *event) override; + private: const ProjectContainer &m_project; GameScene m_scene; @@ -28,6 +31,7 @@ private: GlGameWindow m_glGameWindow; VulkanGameWindow m_vulkanGameWindow; bool m_vulkanAvailable{}; + QWindow *m_mouseSource{}; QTimer m_frameTimer; QElapsedTimer m_elapsed; qint64 m_nextFrameNanoseconds{}; diff --git a/src/engine/gamescene.cpp b/src/engine/gamescene.cpp index 93dd82d..bf7940c 100644 --- a/src/engine/gamescene.cpp +++ b/src/engine/gamescene.cpp @@ -26,6 +26,37 @@ QPointF motion(double degrees, double speed) return {speed * std::cos(radians), -speed * std::sin(radians)}; } +std::optional mouseButtonEvent(Qt::MouseButton button, bool pressed, bool global) +{ + using Event = Object::EventType; + switch (button) + { + case Qt::LeftButton: + return global ? (pressed ? Event::MouseGlobalLeftPressed : Event::MouseGlobalLeftReleased) + : (pressed ? Event::MouseLeftPressed : Event::MouseLeftReleased); + case Qt::RightButton: + return global ? (pressed ? Event::MouseGlobalRightPressed : Event::MouseGlobalRightReleased) + : (pressed ? Event::MouseRightPressed : Event::MouseRightReleased); + case Qt::MiddleButton: + return global ? (pressed ? Event::MouseGlobalMiddlePressed : Event::MouseGlobalMiddleReleased) + : (pressed ? Event::MouseMiddlePressed : Event::MouseMiddleReleased); + default: + return std::nullopt; + } +} + +std::optional mouseHeldEvent(Qt::MouseButton button, bool global) +{ + using Event = Object::EventType; + switch (button) + { + case Qt::LeftButton: return global ? Event::MouseGlobalLeftButton : Event::MouseLeftButton; + case Qt::RightButton: return global ? Event::MouseGlobalRightButton : Event::MouseRightButton; + case Qt::MiddleButton: return global ? Event::MouseGlobalMiddleButton : Event::MouseMiddleButton; + default: return std::nullopt; + } +} + } // namespace void ObjectInstance::executeActions(const ActionsContainer &actions, GameScene &scene, @@ -260,6 +291,115 @@ void GameScene::removeDestroyed() std::erase_if(instances, [](const Instance &instance) { return instance.pendingDestroy; }); } +void GameScene::queueMouseMove(QPointF position) +{ + m_mouseInputs.push_back({MouseInput::Kind::Move, position}); +} + +void GameScene::queueMouseButton(QPointF position, Qt::MouseButton button, bool pressed) +{ + m_mouseInputs.push_back({pressed ? MouseInput::Kind::Press : MouseInput::Kind::Release, + position, button}); +} + +void GameScene::queueMouseWheel(QPointF position, int angleDeltaY) +{ + m_mouseInputs.push_back({MouseInput::Kind::Wheel, position, Qt::NoButton, angleDeltaY}); +} + +void GameScene::queueMouseLeave() +{ + m_mouseInputs.push_back({MouseInput::Kind::Leave, {}}); +} + +void GameScene::resetMouseInput() +{ + m_mouseInputs.push_back({MouseInput::Kind::Reset, {}}); +} + +void GameScene::dispatchMouseEvent(Object::EventType type, QPointF position, bool global) +{ + for (Instance &instance : instances) + if (!instance.pendingDestroy && + (global || collisionRect(instance).contains(position))) + instance.executeEvent(type, *this, m_executor); +} + +void GameScene::updateMouseHover() +{ + for (Instance &instance : instances) + { + if (instance.pendingDestroy) + continue; + const bool over = m_mouseInside && collisionRect(instance).contains(m_mousePosition); + if (over == instance.mouseOver) + continue; + instance.mouseOver = over; + instance.executeEvent(over ? Object::EventType::MouseEnter : Object::EventType::MouseLeave, + *this, m_executor); + } +} + +void GameScene::processMouseInput() +{ + const auto inputs = std::exchange(m_mouseInputs, {}); + const QRectF room{QPointF{}, QSizeF{roomSize}}; + for (const MouseInput &input : inputs) + { + if (input.kind == MouseInput::Kind::Leave || input.kind == MouseInput::Kind::Reset) + { + m_mouseInside = false; + if (input.kind == MouseInput::Kind::Reset) + m_mouseButtons = Qt::NoButton; + updateMouseHover(); + continue; + } + + m_mousePosition = input.position; + m_mouseInside = room.contains(input.position); + updateMouseHover(); + if (input.kind == MouseInput::Kind::Press || input.kind == MouseInput::Kind::Release) + { + const bool pressed = input.kind == MouseInput::Kind::Press; + if (pressed) + m_mouseButtons |= input.button; + else + m_mouseButtons &= ~Qt::MouseButtons{input.button}; + if (const auto local = mouseButtonEvent(input.button, pressed, false); local && m_mouseInside) + dispatchMouseEvent(*local, input.position, false); + if (const auto global = mouseButtonEvent(input.button, pressed, true); global && m_mouseInside) + dispatchMouseEvent(*global, input.position, true); + } + else if (input.kind == MouseInput::Kind::Wheel && m_mouseInside) + { + m_wheelRemainder += input.wheelDelta; + while (m_wheelRemainder >= 120) + { + dispatchMouseEvent(Object::EventType::MouseWheelUp, input.position, true); + m_wheelRemainder -= 120; + } + while (m_wheelRemainder <= -120) + { + dispatchMouseEvent(Object::EventType::MouseWheelDown, input.position, true); + m_wheelRemainder += 120; + } + } + } + + updateMouseHover(); // Instances can also move under a stationary cursor. + if (!m_mouseInside) + return; + if (m_mouseButtons == Qt::NoButton) + dispatchMouseEvent(Object::EventType::MouseNoButton, m_mousePosition, false); + for (Qt::MouseButton button : {Qt::LeftButton, Qt::RightButton, Qt::MiddleButton}) + { + if (!(m_mouseButtons & button)) + continue; + dispatchMouseEvent(*mouseHeldEvent(button, false), m_mousePosition, false); + dispatchMouseEvent(*mouseHeldEvent(button, true), m_mousePosition, true); + } +} + void GameScene::advanceTo(qint64 elapsedMilliseconds) { const qint64 targetStep = elapsedMilliseconds * roomSpeed / 1000; @@ -284,6 +424,7 @@ void GameScene::advanceTo(qint64 elapsedMilliseconds) break; } } + processMouseInput(); for (Instance &instance : instances) if (!instance.pendingDestroy) instance.executeEvent(Object::EventType::Step, *this, m_executor); @@ -295,6 +436,7 @@ void GameScene::advanceTo(qint64 elapsedMilliseconds) for (Instance &instance : instances) if (!instance.pendingDestroy) instance.step(); + updateMouseHover(); // Detect every pair at its moved position before collision actions can // change either instance. A sprite-size rectangle is our first mask. diff --git a/src/engine/gamescene.h b/src/engine/gamescene.h index 96db84c..c30a692 100644 --- a/src/engine/gamescene.h +++ b/src/engine/gamescene.h @@ -11,6 +11,7 @@ #include #include #include +#include #include "projectcontainer.h" @@ -39,6 +40,7 @@ struct ObjectInstance int depth{}; bool visible{true}; bool pendingDestroy{}; + bool mouseOver{}; std::array alarms; void step(); @@ -71,6 +73,11 @@ struct GameScene std::vector drawOrder() const; void start(const ActionExecutor &executor); void destroyInstance(Instance &instance); + void queueMouseMove(QPointF position); + void queueMouseButton(QPointF position, Qt::MouseButton button, bool pressed); + void queueMouseWheel(QPointF position, int angleDeltaY); + void queueMouseLeave(); + void resetMouseInput(); void advanceTo(qint64 elapsedMilliseconds); void draw(); @@ -82,7 +89,23 @@ struct GameScene std::vector instances; private: + struct MouseInput + { + enum class Kind { Move, Press, Release, Wheel, Leave, Reset } kind; + QPointF position; + Qt::MouseButton button{Qt::NoButton}; + int wheelDelta{}; + }; + qint64 m_stepsDone{}; ActionExecutor m_executor; + std::vector m_mouseInputs; + QPointF m_mousePosition; + Qt::MouseButtons m_mouseButtons{Qt::NoButton}; + bool m_mouseInside{}; + int m_wheelRemainder{}; void removeDestroyed(); + void updateMouseHover(); + void dispatchMouseEvent(Object::EventType type, QPointF position, bool global); + void processMouseInput(); }; diff --git a/src/projectcontainer.h b/src/projectcontainer.h index 9123673..ee378a9 100644 --- a/src/projectcontainer.h +++ b/src/projectcontainer.h @@ -262,6 +262,53 @@ struct Object BeginStep, EndStep, Draw, + MouseLeftButton, + MouseRightButton, + MouseMiddleButton, + MouseNoButton, + MouseLeftPressed, + MouseRightPressed, + MouseMiddlePressed, + MouseLeftReleased, + MouseRightReleased, + MouseMiddleReleased, + MouseEnter, + MouseLeave, + MouseWheelUp, + MouseWheelDown, + MouseGlobalLeftButton, + MouseGlobalRightButton, + MouseGlobalMiddleButton, + MouseGlobalLeftPressed, + MouseGlobalRightPressed, + MouseGlobalMiddlePressed, + MouseGlobalLeftReleased, + MouseGlobalRightReleased, + MouseGlobalMiddleReleased, + MouseJoystick1Left, + MouseJoystick1Right, + MouseJoystick1Up, + MouseJoystick1Down, + MouseJoystick1Button1, + MouseJoystick1Button2, + MouseJoystick1Button3, + MouseJoystick1Button4, + MouseJoystick1Button5, + MouseJoystick1Button6, + MouseJoystick1Button7, + MouseJoystick1Button8, + MouseJoystick2Left, + MouseJoystick2Right, + MouseJoystick2Up, + MouseJoystick2Down, + MouseJoystick2Button1, + MouseJoystick2Button2, + MouseJoystick2Button3, + MouseJoystick2Button4, + MouseJoystick2Button5, + MouseJoystick2Button6, + MouseJoystick2Button7, + MouseJoystick2Button8, }; using events_container_t = std::map;