mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-25 18:30:21 +00:00
229 lines
6.6 KiB
C++
229 lines
6.6 KiB
C++
#include "scantargetwidget.h"
|
|
|
|
#include <QApplication>
|
|
#include <QCursor>
|
|
#include <QDebug>
|
|
#include <QDesktopWidget>
|
|
#include <QGuiApplication>
|
|
#include <QKeyEvent>
|
|
#include <QMouseEvent>
|
|
#include <QOpenGLContext>
|
|
#include <QScreen>
|
|
#include <QTimer>
|
|
|
|
#include "../../ClockReceiver/TimeTypes.hpp"
|
|
|
|
ScanTargetWidget::ScanTargetWidget(QWidget *parent) : QOpenGLWidget(parent) {}
|
|
ScanTargetWidget::~ScanTargetWidget() {}
|
|
|
|
void ScanTargetWidget::initializeGL() {
|
|
setDefaultClearColour();
|
|
|
|
// Follow each swapped frame with an additional update.
|
|
connect(this, &QOpenGLWidget::frameSwapped, this, &ScanTargetWidget::vsync);
|
|
}
|
|
|
|
void ScanTargetWidget::paintGL() {
|
|
if(requestedRedrawTime) {
|
|
const auto now = Time::nanos_now();
|
|
vsyncPredictor.add_timer_jitter(now - requestedRedrawTime);
|
|
requestedRedrawTime = 0;
|
|
}
|
|
|
|
// TODO: if Qt 5.14 can be guaranteed, just use window()->screen().
|
|
const auto screenNumber = QApplication::desktop()->screenNumber(this);
|
|
QScreen *const screen = QGuiApplication::screens()[screenNumber];
|
|
|
|
const float newOutputScale = float(screen->devicePixelRatio());
|
|
if(outputScale != newOutputScale) {
|
|
outputScale = newOutputScale;
|
|
resize();
|
|
}
|
|
vsyncPredictor.set_frame_rate(float(screen->refreshRate()));
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
// Gmynastics ahoy: if a producer has been specified or previously connected then:
|
|
//
|
|
// (i) if it's a new producer, generate a new scan target and pass it on;
|
|
// (ii) in any case, check whether the underlyiung framebuffer has changed; and
|
|
// (iii) draw.
|
|
//
|
|
// The slightly convoluted scan target forwarding arrangement works around an issue
|
|
// with QOpenGLWidget under macOS, which I did not fully diagnose, in which creating
|
|
// a scan target in ::initializeGL did not work (and no other arrangement really works
|
|
// with regard to starting up).
|
|
if(isConnected || producer) {
|
|
if(producer) {
|
|
isConnected = true;
|
|
framebuffer = defaultFramebufferObject();
|
|
scanTarget = std::make_unique<Outputs::Display::OpenGL::ScanTarget>(framebuffer);
|
|
producer->set_scan_target(scanTarget.get());
|
|
producer = nullptr;
|
|
}
|
|
|
|
// Qt reserves the right to change the framebuffer object due to window resizes or if setParent is called;
|
|
// therefore check whether it has changed.
|
|
const auto newFramebuffer = defaultFramebufferObject();
|
|
if(framebuffer != newFramebuffer) {
|
|
framebuffer = newFramebuffer;
|
|
scanTarget->set_target_framebuffer(framebuffer);
|
|
}
|
|
|
|
vsyncPredictor.begin_redraw();
|
|
scanTarget->update(scaledWidth, scaledHeight);
|
|
scanTarget->draw(scaledWidth, scaledHeight);
|
|
glFinish(); // Make sure all costs are properly accounted for in the vsync predictor.
|
|
vsyncPredictor.end_redraw();
|
|
}
|
|
}
|
|
|
|
void ScanTargetWidget::vsync() {
|
|
if(!isConnected) return;
|
|
|
|
vsyncPredictor.announce_vsync();
|
|
|
|
const auto time_now = Time::nanos_now();
|
|
requestedRedrawTime = vsyncPredictor.suggested_draw_time();
|
|
const auto delay_time = (requestedRedrawTime - time_now) / 1'000'000;
|
|
if(delay_time > 0 && delay_time < vsyncPredictor.frame_duration()) {
|
|
QTimer::singleShot(delay_time, this, SLOT(repaint()));
|
|
} else {
|
|
requestedRedrawTime = 0;
|
|
repaint();
|
|
}
|
|
}
|
|
|
|
void ScanTargetWidget::resizeGL(int w, int h) {
|
|
if(rawWidth != w || rawHeight != h) {
|
|
rawWidth = w;
|
|
rawHeight = h;
|
|
resize();
|
|
}
|
|
}
|
|
|
|
void ScanTargetWidget::resize() {
|
|
const int newScaledWidth = int(float(rawWidth) * outputScale);
|
|
const int newScaledHeight = int(float(rawHeight) * outputScale);
|
|
|
|
if(newScaledWidth != scaledWidth || newScaledHeight != scaledHeight) {
|
|
scaledWidth = newScaledWidth;
|
|
scaledHeight = newScaledHeight;
|
|
glViewport(0, 0, scaledWidth, scaledHeight);
|
|
}
|
|
}
|
|
|
|
void ScanTargetWidget::setScanProducer(MachineTypes::ScanProducer *producer) {
|
|
this->producer = producer;
|
|
repaint();
|
|
}
|
|
|
|
void ScanTargetWidget::stop() {
|
|
makeCurrent();
|
|
scanTarget.reset();
|
|
isConnected = false;
|
|
setDefaultClearColour();
|
|
vsyncPredictor.pause();
|
|
requestedRedrawTime = 0;
|
|
repaint();
|
|
}
|
|
|
|
void ScanTargetWidget::setDefaultClearColour() {
|
|
// Retain the default background colour.
|
|
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::keyReleaseEvent(QKeyEvent *event) {
|
|
// Releasing F8 or F12 needs to be tracked but doesn't actively do anything,
|
|
// so I'm counting that as a Qt ignore.
|
|
if(event->key() == Qt::Key_F8) f8State = false;
|
|
if(event->key() == Qt::Key_F12) f12State = false;
|
|
event->ignore();
|
|
}
|
|
|
|
void ScanTargetWidget::keyPressEvent(QKeyEvent *event) {
|
|
// Use either CTRL+Escape or F8+F12 to end mouse captured mode, if currently captured;
|
|
// otherwise ignore the event.
|
|
|
|
if(event->key() == Qt::Key_F8) f8State = true;
|
|
if(event->key() == Qt::Key_F12) f12State = true;
|
|
|
|
if(mouseIsCaptured && (
|
|
(event->key() == Qt::Key_Escape && event->modifiers()&Qt::ControlModifier) ||
|
|
(f8State && f12State)
|
|
)) {
|
|
releaseMouse();
|
|
|
|
QCursor cursor;
|
|
cursor.setShape(Qt::ArrowCursor);
|
|
setCursor(cursor);
|
|
return;
|
|
}
|
|
|
|
event->ignore();
|
|
}
|
|
|
|
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;
|
|
}
|