diff --git a/OSBindings/Qt/mainwindow.cpp b/OSBindings/Qt/mainwindow.cpp index 832661afd..f4bf5eeb8 100644 --- a/OSBindings/Qt/mainwindow.cpp +++ b/OSBindings/Qt/mainwindow.cpp @@ -709,30 +709,41 @@ void MainWindow::setUIPhase(UIPhase phase) { ui->topTipLabel->setVisible(phase == UIPhase::SelectingMachine); // Consider setting a window title, if it's knowable. - switch(phase) { - case UIPhase::SelectingMachine: - setWindowTitle(tr("Select a machine...")); - break; - case UIPhase::RequestingROMs: - setWindowTitle(tr("Provide ROMs...")); - break; - - default: - // Update the window title. TODO: clearly I need a proper functional solution for the window title. - if(openFileName.isEmpty()) { - const auto machineType = targets[0]->machine; - setWindowTitle(QString::fromStdString(Machine::LongNameForTargetMachine(machineType))); - } else { - setWindowTitle(openFileName); - } - break; - } + setWindowTitle(); // Set appropriate focus if necessary; e.g. this ensures that machine-picker // widgets aren't still selectable after a machine starts. if(phase != UIPhase::SelectingMachine) { ui->openGLWidget->setFocus(); } + + // Indicate whether to catch mouse input. + ui->openGLWidget->setMouseDelegate( + (phase == UIPhase::RunningMachine && machine && machine->mouse_machine()) ? this : nullptr + ); +} + +void MainWindow::setWindowTitle() { + QString title; + + switch(uiPhase) { + case UIPhase::SelectingMachine: title = tr("Select a machine..."); break; + case UIPhase::RequestingROMs: title = tr("Provide ROMs..."); break; + + default: + // Update the window title. TODO: clearly I need a proper functional solution for the window title. + if(openFileName.isEmpty()) { + const auto machineType = targets[0]->machine; + title = QString::fromStdString(Machine::LongNameForTargetMachine(machineType)); + } else { + title = openFileName; + } + break; + } + + if(mouseIsCaptured) title += " (press control+escape to release mouse)"; + + QMainWindow::setWindowTitle(title); } // MARK: - Event Processing @@ -817,6 +828,27 @@ bool MainWindow::processEvent(QKeyEvent *event) { return false; } +void MainWindow::setMouseIsCaptured(bool isCaptured) { + mouseIsCaptured = isCaptured; + setWindowTitle(); +} + +void MainWindow::moveMouse(QPoint vector) { + std::unique_lock lock(machineMutex); + auto mouseMachine = machine->mouse_machine(); + if(!mouseMachine) return; + + mouseMachine->get_mouse().move(vector.x(), vector.y()); +} + +void MainWindow::setButtonPressed(int index, bool isPressed) { + std::unique_lock lock(machineMutex); + auto mouseMachine = machine->mouse_machine(); + if(!mouseMachine) return; + + mouseMachine->get_mouse().set_button_pressed(index, isPressed); +} + // MARK: - New Machine Creation #include "../../Analyser/Static/Acorn/Target.hpp" diff --git a/OSBindings/Qt/mainwindow.h b/OSBindings/Qt/mainwindow.h index 98ee64c99..47c319644 100644 --- a/OSBindings/Qt/mainwindow.h +++ b/OSBindings/Qt/mainwindow.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE -class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegate { +class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegate, public ScanTargetWidget::MouseDelegate { Q_OBJECT void createActions(); @@ -35,6 +35,10 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat void keyPressEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; + void setMouseIsCaptured(bool) override; + void moveMouse(QPoint) override; + void setButtonPressed(int index, bool isPressed) override; + private: std::unique_ptr ui; std::unique_ptr timer; @@ -117,6 +121,9 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat void addAtari2600Menu(); void toggleAtari2600Switch(Atari2600Switch toggleSwitch); + + void setWindowTitle(); + bool mouseIsCaptured = false; }; #endif // MAINWINDOW_H diff --git a/OSBindings/Qt/scantargetwidget.cpp b/OSBindings/Qt/scantargetwidget.cpp index 9a0da03db..00f11e492 100644 --- a/OSBindings/Qt/scantargetwidget.cpp +++ b/OSBindings/Qt/scantargetwidget.cpp @@ -1,9 +1,12 @@ #include "scantargetwidget.h" #include +#include #include #include #include +#include +#include #include #include #include @@ -21,10 +24,10 @@ void ScanTargetWidget::initializeGL() { } void ScanTargetWidget::paintGL() { - if(requested_redraw_time_) { + if(requestedRedrawTime) { const auto now = Time::nanos_now(); - vsyncPredictor.add_timer_jitter(now - requested_redraw_time_); - requested_redraw_time_ = 0; + vsyncPredictor.add_timer_jitter(now - requestedRedrawTime); + requestedRedrawTime = 0; } // TODO: if Qt 5.14 can be guaranteed, just use window()->screen(). @@ -81,27 +84,27 @@ void ScanTargetWidget::vsync() { vsyncPredictor.announce_vsync(); const auto time_now = Time::nanos_now(); - requested_redraw_time_ = vsyncPredictor.suggested_draw_time(); - const auto delay_time = (requested_redraw_time_ - time_now) / 1'000'000; + requestedRedrawTime = vsyncPredictor.suggested_draw_time(); + const auto delay_time = (requestedRedrawTime - time_now) / 1'000'000; if(delay_time > 0) { QTimer::singleShot(delay_time, this, SLOT(repaint())); } else { - requested_redraw_time_ = 0; + requestedRedrawTime = 0; repaint(); } } void ScanTargetWidget::resizeGL(int w, int h) { - if(width != w || height != h) { - width = w; - height = h; + if(rawWidth != w || rawHeight != h) { + rawWidth = w; + rawHeight = h; resize(); } } void ScanTargetWidget::resize() { - const int newScaledWidth = int(float(width) * outputScale); - const int newScaledHeight = int(float(height) * outputScale); + const int newScaledWidth = int(float(rawWidth) * outputScale); + const int newScaledHeight = int(float(rawHeight) * outputScale); if(newScaledWidth != scaledWidth || newScaledHeight != scaledHeight) { scaledWidth = newScaledWidth; @@ -121,7 +124,7 @@ void ScanTargetWidget::stop() { isConnected = false; setDefaultClearColour(); vsyncPredictor.pause(); - requested_redraw_time_ = 0; + requestedRedrawTime = 0; repaint(); } @@ -130,3 +133,76 @@ void ScanTargetWidget::setDefaultClearColour() { const QColor backgroundColour = palette().color(QWidget::backgroundRole()); glClearColor(backgroundColour.redF(), backgroundColour.greenF(), backgroundColour.blueF(), 1.0); } + +void ScanTargetWidget::setMouseDelegate(MouseDelegate *delegate) { + if(!delegate && mouseIsCaptured) { + releaseMouse(); + } + mouseDelegate = delegate; + setMouseTracking(delegate); +} + +void ScanTargetWidget::keyPressEvent(QKeyEvent *event) { + if(mouseIsCaptured && event->key() == Qt::Key_Escape && event->modifiers()&Qt::ControlModifier) { + releaseMouse(); + + QCursor cursor; + cursor.setShape(Qt::ArrowCursor); + setCursor(cursor); + } +} + +void ScanTargetWidget::releaseMouse() { + QOpenGLWidget::releaseMouse(); + mouseIsCaptured = false; + mouseDelegate->setMouseIsCaptured(false); +} + +void ScanTargetWidget::mousePressEvent(QMouseEvent *event) { + if(mouseDelegate) { + if(!mouseIsCaptured) { + mouseIsCaptured = true; + grabMouse(); + + QCursor cursor; + cursor.setPos(mapToGlobal(QPoint(width() / 2, height() / 2))); + cursor.setShape(Qt::BlankCursor); + setCursor(cursor); + + mouseDelegate->setMouseIsCaptured(true); + } else { + setMouseButtonPressed(event->button(), true); + } + } +} + +void ScanTargetWidget::mouseReleaseEvent(QMouseEvent *event) { + if(mouseDelegate && !mouseIsCaptured) { + setMouseButtonPressed(event->button(), false); + } +} + +void ScanTargetWidget::setMouseButtonPressed(Qt::MouseButton button, bool isPressed) { + switch(button) { + default: break; + case Qt::LeftButton: mouseDelegate->setButtonPressed(0, isPressed); break; + case Qt::RightButton: mouseDelegate->setButtonPressed(1, isPressed); break; + case Qt::MiddleButton: mouseDelegate->setButtonPressed(2, isPressed); break; + } +} + +void ScanTargetWidget::mouseMoveEvent(QMouseEvent *event) { + // Recentre the mouse cursor upon every move if it is currently captured. + if(mouseDelegate && mouseIsCaptured) { + const QPoint centre = QPoint(width() / 2, height() / 2); + const QPoint vector = event->pos() - centre; + + mouseDelegate->moveMouse(vector); + + QCursor::setPos(mapToGlobal(centre)); + } +} + +bool ScanTargetWidget::isMouseCaptured() { + return mouseIsCaptured; +} diff --git a/OSBindings/Qt/scantargetwidget.h b/OSBindings/Qt/scantargetwidget.h index d1df0d555..85edf8ada 100644 --- a/OSBindings/Qt/scantargetwidget.h +++ b/OSBindings/Qt/scantargetwidget.h @@ -8,23 +8,44 @@ #include "../../ClockReceiver/VSyncPredictor.hpp" -class ScanTargetWidget : public QOpenGLWidget -{ +class ScanTargetWidget : public QOpenGLWidget { public: ScanTargetWidget(QWidget *parent = nullptr); ~ScanTargetWidget(); - // Sets the current scan producer; this scan producer will be - // handed a suitable scan target as soon as one exists. + /// Sets the current scan producer; this scan producer will be + /// handed a suitable scan target as soon as one exists. void setScanProducer(MachineTypes::ScanProducer *); + /// Destructs the current scan target void stop(); + struct MouseDelegate { + virtual void setMouseIsCaptured(bool) = 0; + virtual void moveMouse(QPoint) = 0; + virtual void setButtonPressed(int index, bool isPressed) = 0; + }; + /// If a delegate is assigned then this widget will respond to clicks by capturing + /// the mouse, unless and until either ::stop() is called or ctrl+escape is pressed. + /// Mouse events can be tracked by the main window while the mouse is captured. + void setMouseDelegate(MouseDelegate *); + + /// @returns @c true if the mouse is currently captured; @c false otherwise. + bool isMouseCaptured(); + protected: void initializeGL() override; void resizeGL(int w, int h) override; void paintGL() override; + void mousePressEvent(QMouseEvent *) override; + void mouseReleaseEvent(QMouseEvent *) override; + void mouseMoveEvent(QMouseEvent *) override; + void keyPressEvent(QKeyEvent *) override; + + void releaseMouse(); + void setMouseButtonPressed(Qt::MouseButton, bool); + private: // This should be created only once there's an OpenGL context. So it // can't be done at creation time. @@ -34,15 +55,18 @@ class ScanTargetWidget : public QOpenGLWidget GLuint framebuffer = 0; MachineTypes::ScanProducer *producer = nullptr; - Time::Nanos requested_redraw_time_ = 0; + Time::Nanos requestedRedrawTime = 0; void setDefaultClearColour(); - int width = 0, height = 0; + int rawWidth = 0, rawHeight = 0; int scaledWidth = 0, scaledHeight = 0; float outputScale = 1.0f; void resize(); + MouseDelegate *mouseDelegate = nullptr; + bool mouseIsCaptured = false; + private slots: void vsync(); };