Skip to content

Commit

Permalink
[TASK] enable hover debouncing
Browse files Browse the repository at this point in the history
This adds a configurable delay (50 ms by default) before labels are
shown. It helps with easing the flashiness when moving over many labels.

The mouse has to not move during that delay for the hover action to
engage.
  • Loading branch information
jonaseberle committed Jun 28, 2023
1 parent 2a1ff83 commit f05022a
Show file tree
Hide file tree
Showing 12 changed files with 429 additions and 201 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yaml
Expand Up @@ -29,8 +29,8 @@ jobs:
run: |
export PATH="$PATH:$(readlink -f temp/uncrustify-*/build/)"
uncrustify --version
bin/uncrustify-check.sh \
|| ( bin/uncrustify-fix.sh; git diff --exit-code )
bin/check-lint.sh \
|| ( bin/fix-lint.sh; git diff --exit-code )
facts:
outputs:
Expand Down
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion src/Airport.cpp
Expand Up @@ -311,7 +311,7 @@ const QString Airport::frequencyString() const {
if (frequencies.contains(c->frequency)) {
continue;
}
controllers << ((c->frequency.length() > 1? c->frequency: c->aliasOrName()) + (suffix.isEmpty()? "": "_" + suffix));
controllers << ((c->frequency.length() > 1? c->frequency: "") + (suffix.isEmpty()? "": "_" + suffix));
frequencies.insert(c->frequency);
}
if (!controllers.empty()) {
Expand Down
142 changes: 92 additions & 50 deletions src/GLWidget.cpp
Expand Up @@ -30,15 +30,22 @@ GLWidget::GLWidget(QGLFormat fmt, QWidget* parent)
_activeAirportLabelZoomTreshold(2.), _inactiveAirportLabelZoomTreshold(.12),
_controllerLabelZoomTreshold(2.5),
_usedWaypointsLabelZoomThreshold(.7),
_xRot(0), _yRot(0), _zRot(0), _zoom(2), _aspectRatio(1),
_highlighter(0) {
_xRot(0), _yRot(0), _zRot(0), _zoom(2), _aspectRatio(1) {
setAutoFillBackground(false);
setMouseTracking(true);

// call default (=9) map position (without triggering a GuiMessage)
restorePosition(9, true);

clientSelection = new ClientSelectionWidget();

m_updateTimer = new QTimer(this);
connect(m_updateTimer, &QTimer::timeout, this, QOverload<>::of(&QWidget::update));
configureUpdateTimer();

m_hoverDebounceTimer = new QTimer(this);
connect(m_hoverDebounceTimer, &QTimer::timeout, this, &GLWidget::updateHoverState);
configureHoverDebounce();
}

GLWidget::~GLWidget() {
Expand Down Expand Up @@ -1321,18 +1328,12 @@ void GLWidget::paintGL() {

// highlight friends
if (Settings::highlightFriends()) {
if (_highlighter == 0) {
createFriendHighlighter();
double dRange = 0;
if (Settings::animateFriendsHighlight()) {
dRange = qSin(QTime::currentTime().msec() / 1000. * M_PI);
}
QTime time = QTime::currentTime();
double range = (time.second() % 5);
range += (time.msec() % 500) / 1000;

double lineWidth = Settings::highlightLineWidth();
if (!Settings::useHighlightAnimation()) {
range = 0;
destroyFriendHighlighter();
}

foreach (const auto &_friend, m_friendPositions) {
if (qFuzzyIsNull(_friend.first) && qFuzzyIsNull(_friend.second)) {
Expand All @@ -1344,8 +1345,8 @@ void GLWidget::paintGL() {
glBegin(GL_LINE_LOOP);
GLdouble circle_distort = qCos(_friend.first * Pi180);
for (int i = 0; i <= 360; i += 10) {
double x = _friend.first + Nm2Deg((80 - (range * 20))) * circle_distort * qCos(i * Pi180);
double y = _friend.second + Nm2Deg((80 - (range * 20))) * qSin(i * Pi180);
double x = _friend.first + Nm2Deg((80 - (dRange * 20))) * circle_distort * qCos(i * Pi180);
double y = _friend.second + Nm2Deg((80 - (dRange * 20))) * qSin(i * Pi180);
VERTEX(x, y);
}
glEnd();
Expand All @@ -1366,11 +1367,19 @@ void GLWidget::paintGL() {
// drawCoordinateAxii(); // debug: see axii (x = red, y = green, z = blue)

if (Settings::showFps()) {
static bool frameToggle = false;
frameToggle = !frameToggle;
const float fps = 1000. / (QDateTime::currentMSecsSinceEpoch() - started);
static bool _frameToggle = false;
_frameToggle = !_frameToggle;
const float _ms = QDateTime::currentMSecsSinceEpoch() - started;
const float _fps = 1000. / (QDateTime::currentMSecsSinceEpoch() - started);
qglColor(Settings::firFontColor());
renderText(0, height() - 2, QString("%1 fps %2").arg(fps, 0, 'f', 0).arg(frameToggle? '*': ' '), Settings::firFont());
renderText(
0,
height() - 2,
QString("%1 fps (%2 ms) %3").arg(_fps, 0, 'f', 0)
.arg(_ms, 0, 'i', 0)
.arg(_frameToggle? '*': ' '),
Settings::firFont()
);
}
glFlush(); // http://www.opengl.org/sdk/docs/man/xhtml/glFlush.xml
}
Expand Down Expand Up @@ -1403,37 +1412,47 @@ void GLWidget::mouseMoveEvent(QMouseEvent* event) {
update();
}

QList<MapObject*> newHoveredObjects;
// suppress while doing map actions
if (!m_isMapMoving && !m_isMapZooming && !m_isMapRectSelecting) {
newHoveredObjects = objectsAt(currentPos.x(), currentPos.y());
}

if (newHoveredObjects != m_hoveredObjects) {
// remove from fontRectangles when not hovered an more
foreach (const auto &o, m_hoveredObjects) {
if (!newHoveredObjects.contains(o)) {
foreach (const auto &fr, m_fontRectangles) {
if (fr.object == o) {
m_fontRectangles.remove(fr);
break;
}
m_newHoveredObjects = objectsAt(currentPos.x(), currentPos.y());
}

// deal with mouseLeave immediately
auto _tmpHoveredObjects = QList<MapObject*>(m_hoveredObjects);
bool _hoveredObjectsDirty = false;
foreach (const auto &o, _tmpHoveredObjects) {
if (!m_newHoveredObjects.contains(o)) {
foreach (const auto &fr, m_fontRectangles) {
if (fr.object == o) {
m_fontRectangles.remove(fr);
m_hoveredObjects.removeOne(o);
_hoveredObjectsDirty = true;
break;
}
}
}

}
if (_hoveredObjectsDirty) {
invalidatePilots(); // for hovered objects' routes
update();
}
m_hoveredObjects = newHoveredObjects;

// deal with everything else later
if (m_newHoveredObjects != m_hoveredObjects) {
m_hoverDebounceTimer->start(); // restart timer
}

// cursor
bool hasPrimaryFunction = false;
foreach (const auto &o, m_hoveredObjects) {
foreach (const auto &o, m_newHoveredObjects) {
if (o->hasPrimaryAction()) {
hasPrimaryFunction = true;
break;
}
}
setCursor(hasPrimaryFunction? Qt::PointingHandCursor: Qt::ArrowCursor);

// sectors, airport controllers
double lat, lon;
if (local2latLon(currentPos.x(), currentPos.y(), lat, lon)) {
QSet<Controller*> _newHoveredControllers;
Expand All @@ -1460,7 +1479,7 @@ void GLWidget::mouseMoveEvent(QMouseEvent* event) {
}
}
// copy from hovered map labels
foreach (const auto &o, m_hoveredObjects) {
foreach (const auto &o, m_newHoveredObjects) {
auto* c = dynamic_cast<Controller*>(o);
if (c != 0 && c->sector != nullptr) {
_newHoveredControllers.insert(c);
Expand Down Expand Up @@ -1556,6 +1575,8 @@ void GLWidget::mouseReleaseEvent(QMouseEvent* event) {
} else if (_mouseDownPos == currentPos && event->button() == Qt::RightButton) {
rightClick(currentPos);
}

mouseMoveEvent(event); // handle inihibited update of hovered objects
update();
}

Expand Down Expand Up @@ -2240,7 +2261,7 @@ void GLWidget::drawTestTextures() {
static QTimer* testTimer;
static float i = 0.;
if (testTimer == 0) {
testTimer = new QTimer();
testTimer = new QTimer(this);
connect(
testTimer, &QTimer::timeout, this, [&] {
i = fmod(i + .1, 30.); update();
Expand Down Expand Up @@ -2316,6 +2337,32 @@ void GLWidget::orthoMatrix(GLfloat lat, GLfloat lon) {
glTranslatef(0, 0, 1);
}

/////////////////////////
// Hover and map object actions
/////////////////////////

void GLWidget::configureHoverDebounce() {
m_hoverDebounceTimer->setSingleShot(true);
m_hoverDebounceTimer->setInterval(Settings::hoverDebounceMs());
}

void GLWidget::updateHoverState() {
// remove from fontRectangles when not hovered any more
foreach (const auto &o, m_hoveredObjects) {
if (!m_newHoveredObjects.contains(o)) {
foreach (const auto &fr, m_fontRectangles) {
if (fr.object == o) {
m_fontRectangles.remove(fr);
break;
}
}
}
}
m_hoveredObjects = m_newHoveredObjects;
invalidatePilots(); // for hovered objects' routes
update();
}

QList<MapObject*> GLWidget::objectsAt(int x, int y, double radiusSimple) const {
QList<MapObject*> result;
foreach (const auto &fr, fontRectanglesPrioritized()) { // scan text labels
Expand Down Expand Up @@ -2546,23 +2593,18 @@ void GLWidget::newWhazzupData(bool isNew) {
qDebug() << "-- finished";
}

void GLWidget::createFriendHighlighter() {
_highlighter = new QTimer(this);
_highlighter->setInterval(100);
connect(_highlighter, &QTimer::timeout, this, QOverload<>::of(&QWidget::update));
_highlighter->start();
}
void GLWidget::configureUpdateTimer() {
const bool isRunFullFps = Settings::highlightFriends() && Settings::animateFriendsHighlight();

void GLWidget::destroyFriendHighlighter() {
if (_highlighter == 0) {
return;
}
if (_highlighter->isActive()) {
_highlighter->stop();
if (isRunFullFps) {
m_updateTimer->setInterval(40);
if (!m_updateTimer->isActive()) {
m_updateTimer->start();
}
} else {
m_updateTimer->stop();
update();
}
disconnect(_highlighter, &QTimer::timeout, this, QOverload<>::of(&QWidget::update));
delete _highlighter;
_highlighter = 0;
}


Expand Down
13 changes: 7 additions & 6 deletions src/GLWidget.h
Expand Up @@ -47,11 +47,10 @@ class GLWidget
void rightClick(const QPoint& pos);
void zoomIn(double factor);
void zoomTo(double _zoom);

void rememberPosition(int nr);
void restorePosition(int nr, bool isSilent = false);

void destroyFriendHighlighter();
void configureHoverDebounce();
void configureUpdateTimer();
signals:
void mapClicked(int x, int y, QPoint absolutePos);
protected:
Expand Down Expand Up @@ -89,8 +88,6 @@ class GLWidget
void parseTexture();
void createLights();

void createFriendHighlighter();

QList<Sector*> m_staticSectors;
QPoint _lastPos, _mouseDownPos;
bool m_isMapMoving, m_isMapZooming, m_isMapRectSelecting, _lightsGenerated;
Expand All @@ -108,7 +105,8 @@ class GLWidget
double _pilotLabelZoomTreshold, _activeAirportLabelZoomTreshold, _inactiveAirportLabelZoomTreshold,
_controllerLabelZoomTreshold, _usedWaypointsLabelZoomThreshold,
_xRot, _yRot, _zRot, _zoom, _aspectRatio;
QTimer* _highlighter;
QTimer* m_updateTimer;
QTimer* m_hoverDebounceTimer;
QList< QPair<double, double> > m_friendPositions;
QList<MapObject*> m_hoveredObjects, m_newHoveredObjects;
QMultiHash<QPair<int, int>, MapObject*> m_inactiveAirportMapObjectsByLatLng;
Expand Down Expand Up @@ -147,6 +145,9 @@ class GLWidget
void drawBillboardWorldSize(GLfloat lat, GLfloat lon, const QSizeF& size);
inline void drawBillboard(GLfloat lat, GLfloat lon, GLfloat halfWidth, GLfloat halfHeight, GLfloat alpha = 1.);
void orthoMatrix(GLfloat lat, GLfloat lon);

private slots:
void updateHoverState();
};

inline bool operator==(const GLWidget::FontRectangle &e1, const GLWidget::FontRectangle &e2) {
Expand Down
11 changes: 9 additions & 2 deletions src/Settings.cpp
Expand Up @@ -639,6 +639,13 @@ void Settings::setShowToolTips(const bool v) {
instance()->setValue("mapUi/showTooltips", v);
}

int Settings::hoverDebounceMs() {
return instance()->value("labelHover/debounceMs", 50).toInt();
}
void Settings::setHoverDebounceMs(int value) {
instance()->setValue("labelHover/debounceMs", value);
}

QColor Settings::coastLineColor() {
return instance()->value("earthSpace/coastLineColor", QColor::fromRgb(102, 85, 67, 200)).value<QColor>();
}
Expand Down Expand Up @@ -1458,10 +1465,10 @@ void Settings::setHighlightLineWidth(double value) {
instance()->setValue("pilotDisplay/highlightLineWidth", value);
}

bool Settings::useHighlightAnimation() {
bool Settings::animateFriendsHighlight() {
return instance()->value("pilotDisplay/useHighlightAnimation", false).toBool();
}
void Settings::setUseHighlightAnimation(bool value) {
void Settings::setAnimateFriendsHighlight(bool value) {
instance()->setValue("pilotDisplay/useHighlightAnimation", value);
}

Expand Down
7 changes: 5 additions & 2 deletions src/Settings.h
Expand Up @@ -214,6 +214,9 @@ class Settings {
static bool showToolTips();
static void setShowToolTips(const bool value);

static int hoverDebounceMs();
static void setHoverDebounceMs(int value);

// sectors

static QColor firBorderLineColor();
Expand Down Expand Up @@ -453,8 +456,8 @@ class Settings {
static double highlightLineWidth();
static void setHighlightLineWidth(double value);

static bool useHighlightAnimation();
static void setUseHighlightAnimation(bool value);
static bool animateFriendsHighlight();
static void setAnimateFriendsHighlight(bool value);

static bool checkForUpdates();
static void setCheckForUpdates(bool value);
Expand Down
20 changes: 18 additions & 2 deletions src/dialogs/PreferencesDialog.cpp
Expand Up @@ -91,6 +91,9 @@ void PreferencesDialog::loadSettings() {
pbLabelHoveredBgDarkColor->setText(color.name());
pbLabelHoveredBgDarkColor->setPalette(QPalette(color));

// Display -> labels
sbHoverDebounce->setValue(Settings::hoverDebounceMs());

// OpenGL
glTextures->setChecked(Settings::glTextures());

Expand Down Expand Up @@ -360,7 +363,7 @@ void PreferencesDialog::loadSettings() {
sb_highlightFriendsLineWidth->setValue(Settings::highlightLineWidth());
pb_highlightFriendsColor->setPalette(QPalette(Settings::friendsHighlightColor()));
pb_highlightFriendsColor->setText(Settings::friendsHighlightColor().name());
cb_Animation->setChecked(Settings::useHighlightAnimation());
cb_Animation->setChecked(Settings::animateFriendsHighlight());

// FINISHED
_settingsLoaded = true;
Expand Down Expand Up @@ -1421,7 +1424,10 @@ void PreferencesDialog::on_cb_Animation_stateChanged(int state) {
if (!_settingsLoaded) {
return;
}
Settings::setUseHighlightAnimation(state == Qt::Checked);
Settings::setAnimateFriendsHighlight(state == Qt::Checked);
if (Window::instance(false) != 0) {
Window::instance()->mapScreen->glWidget->configureUpdateTimer();
}
}

void PreferencesDialog::on_pb_highlightFriendsColor_clicked() {
Expand Down Expand Up @@ -1719,3 +1725,13 @@ void PreferencesDialog::on_cbLabelAlwaysBackdrop_toggled(bool checked) {
}
Settings::setLabelAlwaysBackdropped(checked);
}

void PreferencesDialog::on_sbHoverDebounce_valueChanged(int v) {
Settings::setHoverDebounceMs(v);
}

void PreferencesDialog::on_pbApplyHover_clicked() {
if (Window::instance(false) != 0) {
Window::instance(false)->mapScreen->glWidget->configureHoverDebounce();
}
}

0 comments on commit f05022a

Please sign in to comment.