From db9467aff3a65adce32f37a44c6f26d0eeb68781 Mon Sep 17 00:00:00 2001 From: Nils Schimmelmann Date: Fri, 13 Feb 2026 16:09:35 -0600 Subject: [PATCH] refactor MapCanvas to QOpenGLWindow for better WebGL2 support - Added platform-specific gesture support for macOS and touch devices - Implemented smooth world-coordinate dragging and zoom Co-authored-by: KasparMetsa --- src/display/MapCanvasData.cpp | 68 +++++++- src/display/MapCanvasData.h | 36 ++-- src/display/mapcanvas.cpp | 308 +++++++++++++++++++++------------- src/display/mapcanvas.h | 37 +++- src/display/mapcanvas_gl.cpp | 17 +- src/display/mapwindow.cpp | 148 +++++++++------- src/display/mapwindow.h | 20 ++- src/mainwindow/mainwindow.cpp | 67 +++++--- src/mainwindow/mainwindow.h | 5 + src/mainwindow/utils.cpp | 4 +- 10 files changed, 455 insertions(+), 255 deletions(-) diff --git a/src/display/MapCanvasData.cpp b/src/display/MapCanvasData.cpp index 822774910..849ab061b 100644 --- a/src/display/MapCanvasData.cpp +++ b/src/display/MapCanvasData.cpp @@ -6,12 +6,16 @@ #include "../opengl/LineRendering.h" +#include #include #include #include +#include +#include #include +#include const MMapper::Array &getAllRoomTints() { @@ -57,6 +61,12 @@ std::optional MapCanvasViewport::project(const glm::vec3 &v) const // // output: world coordinates. glm::vec3 MapCanvasViewport::unproject_raw(const glm::vec3 &mouse_depth) const +{ + return unproject_raw(mouse_depth, m_viewProj); +} + +glm::vec3 MapCanvasViewport::unproject_raw(const glm::vec3 &mouse_depth, + const glm::mat4 &viewProj) const { const float depth = mouse_depth.z; assert(::isClamped(depth, 0.f, 1.f)); @@ -67,7 +77,7 @@ glm::vec3 MapCanvasViewport::unproject_raw(const glm::vec3 &mouse_depth) const const glm::vec3 screen{screen2d, depth}; const auto ndc = screen * 2.f - 1.f; - const auto tmp = glm::inverse(m_viewProj) * glm::vec4(ndc, 1.f); + const auto tmp = glm::inverse(viewProj) * glm::vec4(ndc, 1.f); // clamp to avoid division by zero constexpr float limit = 1e-6f; const auto w = (std::abs(tmp.w) < limit) ? std::copysign(limit, tmp.w) : tmp.w; @@ -79,29 +89,54 @@ glm::vec3 MapCanvasViewport::unproject_raw(const glm::vec3 &mouse_depth) const // note: the returned coordinate may not be visible, // because it could be glm::vec3 MapCanvasViewport::unproject_clamped(const glm::vec2 &mouse) const +{ + return unproject_clamped(mouse, m_viewProj); +} + +glm::vec3 MapCanvasViewport::unproject_clamped(const glm::vec2 &mouse, + const glm::mat4 &viewProj) const { const auto flayer = static_cast(m_currentLayer); const auto &x = mouse.x; const auto &y = mouse.y; - const auto a = unproject_raw(glm::vec3{x, y, 0.f}); // near - const auto b = unproject_raw(glm::vec3{x, y, 1.f}); // far + const auto a = unproject_raw(glm::vec3{x, y, 0.f}, viewProj); // near + const auto b = unproject_raw(glm::vec3{x, y, 1.f}, viewProj); // far const float t = (flayer - a.z) / (b.z - a.z); const auto result = glm::mix(a, b, std::clamp(t, 0.f, 1.f)); return glm::vec3{glm::vec2{result}, flayer}; } -glm::vec2 MapCanvasViewport::getMouseCoords(const QInputEvent *const event) const +std::optional MapCanvasViewport::getMouseCoords(const QInputEvent *const event) const { if (const auto *const mouse = dynamic_cast(event)) { - const auto x = static_cast(mouse->pos().x()); - const auto y = static_cast(height() - mouse->pos().y()); + const auto x = static_cast(mouse->position().x()); + const auto y = static_cast(height() - mouse->position().y()); return glm::vec2{x, y}; } else if (const auto *const wheel = dynamic_cast(event)) { const auto x = static_cast(wheel->position().x()); const auto y = static_cast(height() - wheel->position().y()); return glm::vec2{x, y}; + } else if (const auto *const gesture = dynamic_cast(event)) { + const auto x = static_cast(gesture->position().x()); + const auto y = static_cast(height() - gesture->position().y()); + return glm::vec2{x, y}; + } else if (const auto *const touch = dynamic_cast(event)) { + const auto &points = touch->points(); + if (points.isEmpty()) { + return std::nullopt; + } + QPointF centroid(0, 0); + for (const auto &p : points) { + centroid += p.position(); + } + centroid /= static_cast(points.size()); + const auto x = static_cast(centroid.x()); + const auto y = static_cast(height() - centroid.y()); + return glm::vec2{x, y}; } else { - throw std::invalid_argument("event"); + qWarning() << "MapCanvasViewport::getMouseCoords: unhandled event type" << event->type(); + assert(false); + return std::nullopt; } } @@ -110,6 +145,14 @@ glm::vec2 MapCanvasViewport::getMouseCoords(const QInputEvent *const event) cons std::optional MapCanvasViewport::unproject(const QInputEvent *const event) const { const auto xy = getMouseCoords(event); + if (!xy) { + return std::nullopt; + } + return unproject(*xy); +} + +std::optional MapCanvasViewport::unproject(const glm::vec2 &xy) const +{ // We don't actually know the depth we're trying to unproject; // technically we're solving for a ray, so we should unproject // two different depths and find where the ray intersects the @@ -130,7 +173,16 @@ std::optional MapCanvasViewport::unproject(const QInputEvent *const e std::optional MapCanvasViewport::getUnprojectedMouseSel(const QInputEvent *const event) const { - const auto opt_v = unproject(event); + const auto xy = getMouseCoords(event); + if (!xy) { + return std::nullopt; + } + return getUnprojectedMouseSel(*xy); +} + +std::optional MapCanvasViewport::getUnprojectedMouseSel(const glm::vec2 &xy) const +{ + const auto opt_v = unproject(xy); if (!opt_v.has_value()) { return std::nullopt; } diff --git a/src/display/MapCanvasData.h b/src/display/MapCanvasData.h index 886c625bf..c1d5e2fb6 100644 --- a/src/display/MapCanvasData.h +++ b/src/display/MapCanvasData.h @@ -20,7 +20,7 @@ #include #include -#include +#include #include #include #include @@ -48,8 +48,6 @@ struct NODISCARD ScaleFactor final private: float m_scaleFactor = 1.f; - // pinch gesture - float m_pinchFactor = 1.f; private: NODISCARD static float clamp(float x) @@ -63,21 +61,10 @@ struct NODISCARD ScaleFactor final public: NODISCARD float getRaw() const { return clamp(m_scaleFactor); } - NODISCARD float getTotal() const { return clamp(m_scaleFactor * m_pinchFactor); } + NODISCARD float getTotal() const { return clamp(m_scaleFactor); } public: void set(const float scale) { m_scaleFactor = clamp(scale); } - void setPinch(const float pinch) - { - // Don't bother to clamp this, since the total is clamped. - m_pinchFactor = pinch; - } - void endPinch() - { - const float total = getTotal(); - m_scaleFactor = total; - m_pinchFactor = 1.f; - } void reset() { *this = ScaleFactor(); } public: @@ -100,7 +87,7 @@ struct NODISCARD ScaleFactor final struct NODISCARD MapCanvasViewport { private: - QWidget &m_sizeWidget; + QWindow &m_window; public: glm::mat4 m_viewProj{1.f}; @@ -109,27 +96,30 @@ struct NODISCARD MapCanvasViewport int m_currentLayer = 0; public: - explicit MapCanvasViewport(QWidget &sizeWidget) - : m_sizeWidget{sizeWidget} + explicit MapCanvasViewport(QWindow &window) + : m_window{window} {} public: - NODISCARD auto width() const { return m_sizeWidget.width(); } - NODISCARD auto height() const { return m_sizeWidget.height(); } + NODISCARD auto width() const { return m_window.width(); } + NODISCARD auto height() const { return m_window.height(); } NODISCARD Viewport getViewport() const { - const auto &r = m_sizeWidget.rect(); - return Viewport{glm::ivec2{r.x(), r.y()}, glm::ivec2{r.width(), r.height()}}; + return Viewport{glm::ivec2{0, 0}, glm::ivec2{m_window.width(), m_window.height()}}; } NODISCARD float getTotalScaleFactor() const { return m_scaleFactor.getTotal(); } public: NODISCARD std::optional project(const glm::vec3 &) const; NODISCARD glm::vec3 unproject_raw(const glm::vec3 &) const; + NODISCARD glm::vec3 unproject_raw(const glm::vec3 &, const glm::mat4 &) const; NODISCARD glm::vec3 unproject_clamped(const glm::vec2 &) const; + NODISCARD glm::vec3 unproject_clamped(const glm::vec2 &, const glm::mat4 &) const; NODISCARD std::optional unproject(const QInputEvent *event) const; + NODISCARD std::optional unproject(const glm::vec2 &xy) const; NODISCARD std::optional getUnprojectedMouseSel(const QInputEvent *event) const; - NODISCARD glm::vec2 getMouseCoords(const QInputEvent *event) const; + NODISCARD std::optional getUnprojectedMouseSel(const glm::vec2 &xy) const; + NODISCARD std::optional getMouseCoords(const QInputEvent *event) const; }; class NODISCARD MapScreen final diff --git a/src/display/mapcanvas.cpp b/src/display/mapcanvas.cpp index 42abf0437..9a79c0d05 100644 --- a/src/display/mapcanvas.cpp +++ b/src/display/mapcanvas.cpp @@ -37,15 +37,18 @@ #include #include #include -#include #include -#include #if defined(_MSC_VER) || defined(__MINGW32__) #undef near // Bad dog, Microsoft; bad dog!!! #undef far // Bad dog, Microsoft; bad dog!!! #endif +namespace { +constexpr float GESTURE_EPSILON = 1e-6f; +constexpr float PINCH_DISTANCE_THRESHOLD = 1e-3f; +} // namespace + using NonOwningPointer = MapCanvas *; NODISCARD static NonOwningPointer &primaryMapCanvas() { @@ -56,9 +59,9 @@ NODISCARD static NonOwningPointer &primaryMapCanvas() MapCanvas::MapCanvas(MapData &mapData, PrespammedPath &prespammedPath, Mmapper2Group &groupManager, - QWidget *const parent) - : QOpenGLWidget{parent} - , MapCanvasViewport{static_cast(*this)} + QWindow *const parent) + : QOpenGLWindow{NoPartialUpdate, parent} + , MapCanvasViewport{static_cast(*this)} , MapCanvasInputState{prespammedPath} , m_mapScreen{static_cast(*this)} , m_opengl{} @@ -72,8 +75,6 @@ MapCanvas::MapCanvas(MapData &mapData, } setCursor(Qt::OpenHandCursor); - grabGesture(Qt::PinchGesture); - setContextMenuPolicy(Qt::CustomContextMenu); } MapCanvas::~MapCanvas() @@ -224,59 +225,14 @@ void MapCanvas::wheelEvent(QWheelEvent *const event) slot_layerUp(); } } else { - const auto zoomAndMaybeRecenter = [this, event](const int numSteps) -> bool { - assert(numSteps != 0); - const auto optCenter = getUnprojectedMouseSel(event); - - // NOTE: Do a regular zoom if the projection failed. - if (!optCenter) { - m_scaleFactor.logStep(numSteps); - return false; // failed to recenter - } - - // Our goal is to keep the point under the mouse constant, - // so we'll do the usual trick: translate to the origin, - // scale/rotate, then translate back. However, the amount - // we translate will differ because zoom changes our height. - const auto newCenter = optCenter->to_vec3(); - const auto oldCenter = m_mapScreen.getCenter(); - - // 1. recenter to mouse location - - const auto delta1 = glm::ivec2(glm::vec2(newCenter - oldCenter) - * static_cast(SCROLL_SCALE)); - - emit sig_mapMove(delta1.x, delta1.y); - - // 2. zoom in - m_scaleFactor.logStep(numSteps); - - // 3. adjust viewport for new projection - setViewportAndMvp(width(), height()); - - // 4. subtract the offset to same mouse coordinate; - // This probably shouldn't ever fail, but let's make it conditional - // (worst case: we're left centered on what we clicked on, - // which will create infuriating overshoots if it ever happens) - if (const auto &optCenter2 = getUnprojectedMouseSel(event)) { - const auto delta2 = glm::ivec2(glm::vec2(optCenter2->to_vec3() - newCenter) - * static_cast(SCROLL_SCALE)); - - emit sig_mapMove(-delta2.x, -delta2.y); - - // NOTE: caller is expected to call update() after this function, - // so we don't have to update the viewport. - } - - return true; - }; - // Change the zoom level - const int numSteps = event->angleDelta().y() / 120; - if (numSteps != 0) { - zoomAndMaybeRecenter(numSteps); - zoomChanged(); - update(); + const int angleDelta = event->angleDelta().y(); + if (angleDelta != 0) { + const float factor = std::pow(ScaleFactor::ZOOM_STEP, + static_cast(angleDelta) / 120.0f); + if (const auto xy = getMouseCoords(event)) { + zoomAt(factor, *xy); + } } } break; @@ -294,46 +250,97 @@ void MapCanvas::slot_onForcedPositionChange() slot_requestUpdate(); } -bool MapCanvas::event(QEvent *const event) +void MapCanvas::touchEvent(QTouchEvent *const event) { - auto tryHandlePinchZoom = [this, event]() -> bool { - if (event->type() != QEvent::Gesture) { - return false; + if (event->type() == QEvent::TouchBegin) { + emit sig_dismissContextMenu(); + } + + const auto &points = event->points(); + if (points.size() == 2) { + const auto &p1 = points[0]; + const auto &p2 = points[1]; + + if (event->type() == QEvent::TouchBegin || p1.state() == QEventPoint::Pressed + || p2.state() == QEventPoint::Pressed) { + m_initialPinchDistance = glm::distance(glm::vec2(static_cast(p1.position().x()), + static_cast(p1.position().y())), + glm::vec2(static_cast(p2.position().x()), + static_cast(p2.position().y()))); + m_lastPinchFactor = 1.f; } - const auto *const gestureEvent = dynamic_cast(event); - if (gestureEvent == nullptr) { - return false; + if (m_initialPinchDistance > PINCH_DISTANCE_THRESHOLD) { + const float currentDistance + = glm::distance(glm::vec2(static_cast(p1.position().x()), + static_cast(p1.position().y())), + glm::vec2(static_cast(p2.position().x()), + static_cast(p2.position().y()))); + const float currentPinchFactor = currentDistance / m_initialPinchDistance; + const float deltaFactor = currentPinchFactor / m_lastPinchFactor; + + handleZoomAtEvent(event, deltaFactor); + m_lastPinchFactor = currentPinchFactor; } - // Zoom in / out - QGesture *const gesture = gestureEvent->gesture(Qt::PinchGesture); - const auto *const pinch = dynamic_cast(gesture); - if (pinch == nullptr) { - return false; + if (event->type() == QEvent::TouchEnd || p1.state() == QEventPoint::Released + || p2.state() == QEventPoint::Released) { + m_initialPinchDistance = 0.f; + m_lastPinchFactor = 1.f; + } + event->accept(); + } else { + if (points.size() > 2) { + // Explicitly ignore more than 2 touch points for pinch zoom. + qDebug() << "MapCanvas::touchEvent: ignoring" << points.size() << "touch points"; } - const QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags(); - if (changeFlags & QPinchGesture::ScaleFactorChanged) { - const auto pinchFactor = static_cast(pinch->totalScaleFactor()); - m_scaleFactor.setPinch(pinchFactor); - if ((false)) { - zoomChanged(); // Don't call this here, because it's not true yet. - } + if (m_initialPinchDistance > 0.f) { + m_initialPinchDistance = 0.f; + m_lastPinchFactor = 1.f; } - if (pinch->state() == Qt::GestureFinished) { - m_scaleFactor.endPinch(); - zoomChanged(); // might not have actually changed + QOpenGLWindow::touchEvent(event); + } +} + +void MapCanvas::handleZoomAtEvent(const QInputEvent *const event, const float deltaFactor) +{ + if (std::abs(deltaFactor - 1.f) > GESTURE_EPSILON) { + if (const auto xy = getMouseCoords(event)) { + zoomAt(deltaFactor, *xy); } - update(); - return true; - }; + } +} + +bool MapCanvas::event(QEvent *const event) +{ + if (event->type() == QEvent::NativeGesture) { + auto *const nativeEvent = static_cast(event); + if (nativeEvent->gestureType() == Qt::ZoomNativeGesture) { + const auto value = static_cast(nativeEvent->value()); + float deltaFactor = 1.f; + if constexpr (CURRENT_PLATFORM == PlatformEnum::Mac) { + // On macOS, event->value() for ZoomNativeGesture is the magnification delta + // since the last event. + deltaFactor += value; + } else { + // On other platforms, it's typically the cumulative scale factor (1.0 at start). + if (nativeEvent->isBeginEvent()) { + m_lastMagnification = 1.f; + } - if (tryHandlePinchZoom()) { - return true; + if (std::abs(m_lastMagnification) > GESTURE_EPSILON) { + deltaFactor = value / m_lastMagnification; + } + m_lastMagnification = value; + } + handleZoomAtEvent(nativeEvent, deltaFactor); + event->accept(); + return true; + } } - return QOpenGLWidget::event(event); + return QOpenGLWindow::event(event); } void MapCanvas::slot_createRoom() @@ -410,19 +417,34 @@ std::shared_ptr MapCanvas::getInfomarkSelection(const MouseSe void MapCanvas::mousePressEvent(QMouseEvent *const event) { + if (event->button() != Qt::RightButton) { + emit sig_dismissContextMenu(); + } + const bool hasLeftButton = (event->buttons() & Qt::LeftButton) != 0u; const bool hasRightButton = (event->buttons() & Qt::RightButton) != 0u; const bool hasCtrl = (event->modifiers() & Qt::CTRL) != 0u; MAYBE_UNUSED const bool hasAlt = (event->modifiers() & Qt::ALT) != 0u; if (hasLeftButton && hasAlt) { - m_altDragState.emplace(AltDragState{event->pos(), cursor()}); + m_altDragState.emplace(AltDragState{event->position().toPoint(), cursor()}); setCursor(Qt::ClosedHandCursor); event->accept(); return; } - m_sel1 = m_sel2 = getUnprojectedMouseSel(event); + const auto optXy = getMouseCoords(event); + if (!optXy) { + return; + } + const auto xy = *optXy; + if (m_canvasMouseMode == CanvasMouseModeEnum::MOVE) { + const auto worldPos = unproject_clamped(xy); + m_sel1 = m_sel2 = MouseSel{Coordinate2f{worldPos.x, worldPos.y}, m_currentLayer}; + } else { + m_sel1 = m_sel2 = getUnprojectedMouseSel(xy); + } + m_mouseLeftPressed = hasLeftButton; m_mouseRightPressed = hasRightButton; @@ -444,6 +466,7 @@ void MapCanvas::mousePressEvent(QMouseEvent *const event) selectionChanged(); } + emit sig_customContextMenuRequested(event->position().toPoint()); m_mouseRightPressed = false; event->accept(); return; @@ -477,7 +500,6 @@ void MapCanvas::mousePressEvent(QMouseEvent *const event) case CanvasMouseModeEnum::RAYPICK_ROOMS: if (hasLeftButton) { - const auto xy = getMouseCoords(event); const auto near = unproject_raw(glm::vec3{xy, 0.f}); const auto far = unproject_raw(glm::vec3{xy, 1.f}); @@ -593,6 +615,12 @@ void MapCanvas::mousePressEvent(QMouseEvent *const event) void MapCanvas::mouseMoveEvent(QMouseEvent *const event) { + const auto optXy = getMouseCoords(event); + if (!optXy) { + return; + } + const auto xy = *optXy; + if (m_altDragState.has_value()) { // The user released the Alt key mid-drag. if (!((event->modifiers() & Qt::ALT) != 0u)) { @@ -606,7 +634,7 @@ void MapCanvas::mouseMoveEvent(QMouseEvent *const event) auto &dragState = m_altDragState.value(); bool angleChanged = false; - const auto pos = event->pos(); + const auto pos = event->position().toPoint(); const auto delta = pos - dragState.lastPos; dragState.lastPos = pos; @@ -643,10 +671,10 @@ void MapCanvas::mouseMoveEvent(QMouseEvent *const event) if (m_canvasMouseMode != CanvasMouseModeEnum::MOVE) { // NOTE: Y is opposite of what you might expect here. - const int vScroll = std::invoke([this, event]() -> int { + const int vScroll = std::invoke([this, &xy]() -> int { const int h = height(); const int MARGIN = std::min(100, h / 4); - const auto y = event->position().y(); + const auto y = static_cast(static_cast(h) - xy.y); if (y < MARGIN) { return SCROLL_SCALE; } else if (y > h - MARGIN) { @@ -655,10 +683,10 @@ void MapCanvas::mouseMoveEvent(QMouseEvent *const event) return 0; } }); - const int hScroll = std::invoke([this, event]() -> int { + const int hScroll = std::invoke([this, &xy]() -> int { const int w = width(); const int MARGIN = std::min(100, w / 4); - const auto x = event->position().x(); + const auto x = static_cast(xy.x); if (x < MARGIN) { return -SCROLL_SCALE; } else if (x > w - MARGIN) { @@ -671,7 +699,7 @@ void MapCanvas::mouseMoveEvent(QMouseEvent *const event) emit sig_continuousScroll(hScroll, vScroll); } - m_sel2 = getUnprojectedMouseSel(event); + m_sel2 = getUnprojectedMouseSel(xy); switch (m_canvasMouseMode) { case CanvasMouseModeEnum::SELECT_INFOMARKS: @@ -694,12 +722,15 @@ void MapCanvas::mouseMoveEvent(QMouseEvent *const event) infomarksChanged(); break; case CanvasMouseModeEnum::MOVE: - if (hasLeftButton && m_mouseLeftPressed && hasSel2() && hasBackup()) { - const Coordinate2i delta - = ((getSel2().pos - getBackup().pos) * static_cast(SCROLL_SCALE)).truncate(); - if (delta.x != 0 || delta.y != 0) { - // negated because dragging to right is scrolling to the left. - emit sig_mapMove(-delta.x, -delta.y); + if (hasLeftButton && m_mouseLeftPressed && m_dragState.has_value()) { + const glm::vec3 currWorldPos = unproject_clamped(xy, m_dragState->startViewProj); + const glm::vec2 delta = glm::vec2(currWorldPos - m_dragState->startWorldPos); + + if (glm::length(delta) > GESTURE_EPSILON) { + const glm::vec2 newWorldCenter = m_dragState->startScroll - delta; + m_scroll = newWorldCenter; + emit sig_onCenter(newWorldCenter); + update(); } } break; @@ -769,6 +800,12 @@ void MapCanvas::mouseMoveEvent(QMouseEvent *const event) void MapCanvas::mouseReleaseEvent(QMouseEvent *const event) { + const auto optXy = getMouseCoords(event); + if (!optXy) { + return; + } + const auto xy = *optXy; + if (m_altDragState.has_value()) { setCursor(m_altDragState->originalCursor); m_altDragState.reset(); @@ -777,7 +814,7 @@ void MapCanvas::mouseReleaseEvent(QMouseEvent *const event) } emit sig_continuousScroll(0, 0); - m_sel2 = getUnprojectedMouseSel(event); + m_sel2 = getUnprojectedMouseSel(xy); if (m_mouseRightPressed) { m_mouseRightPressed = false; @@ -842,18 +879,16 @@ void MapCanvas::mouseReleaseEvent(QMouseEvent *const event) m_mouseLeftPressed = false; } // Display a room info tooltip if there was no mouse movement - if (hasSel1() && hasSel2() && getSel1().to_vec3() == getSel2().to_vec3()) { + if (event->button() == Qt::LeftButton && hasSel1() && hasSel2() + && getSel1().to_vec3() == getSel2().to_vec3()) { if (const auto room = m_data.findRoomHandle(getSel1().getCoordinate())) { // Tooltip doesn't support ANSI, and there's no way to add formatted text. auto message = mmqt::previewRoom(room, mmqt::StripAnsiEnum::Yes, mmqt::PreviewStyleEnum::ForDisplay); - QToolTip::showText(mapToGlobal(event->position().toPoint()), - message, - this, - rect(), - 5000); + const auto pos = event->position().toPoint(); + emit sig_showTooltip(message, pos); } } break; @@ -998,14 +1033,57 @@ void MapCanvas::mouseReleaseEvent(QMouseEvent *const event) m_ctrlPressed = false; } -QSize MapCanvas::minimumSizeHint() const +void MapCanvas::startMoving(const MouseSel &startPos) +{ + MapCanvasInputState::startMoving(startPos); + m_dragState.emplace(DragState{startPos.to_vec3(), m_scroll, m_viewProj}); +} + +void MapCanvas::stopMoving() { - return {sizeHint().width() / 4, sizeHint().height() / 4}; + MapCanvasInputState::stopMoving(); + m_dragState.reset(); } -QSize MapCanvas::sizeHint() const +void MapCanvas::zoomAt(const float factor, const glm::vec2 &mousePos) { - return {1280, 720}; + const auto optWorldPos = unproject(mousePos); + if (!optWorldPos) { + m_scaleFactor *= factor; + zoomChanged(); + update(); + return; + } + + const glm::vec2 worldPos = glm::vec2(*optWorldPos); + + // Save current state + const glm::vec2 oldScroll = m_scroll; + + // Apply zoom + m_scaleFactor *= factor; + zoomChanged(); + + // Calculate new scroll position to keep worldPos under mousePos. + // We update the viewport and MVP to the new zoom level temporarily + // to perform the unprojection. + setViewportAndMvp(width(), height()); + + const auto optNewWorldPos = unproject(mousePos); + if (optNewWorldPos) { + const glm::vec2 delta = worldPos - glm::vec2(*optNewWorldPos); + const glm::vec2 newScroll = oldScroll + delta; + m_scroll = newScroll; + emit sig_onCenter(newScroll); + } else { + // Fallback: if we can't find the new world position, just stay where we were. + m_scroll = oldScroll; + emit sig_onCenter(oldScroll); + } + + // Refresh the viewport matrix with the final scroll and zoom before painting. + setViewportAndMvp(width(), height()); + update(); } void MapCanvas::slot_setScroll(const glm::vec2 &worldPos) @@ -1051,7 +1129,9 @@ void MapCanvas::onMovement() { const Coordinate &pos = m_data.tryGetPosition().value_or(Coordinate{}); m_currentLayer = pos.z; - emit sig_onCenter(pos.to_vec2() + glm::vec2{0.5f, 0.5f}); + const glm::vec2 newScroll = pos.to_vec2() + glm::vec2{0.5f, 0.5f}; + m_scroll = newScroll; + emit sig_onCenter(newScroll); update(); } diff --git a/src/display/mapcanvas.h b/src/display/mapcanvas.h index cee1e14a0..09459d637 100644 --- a/src/display/mapcanvas.h +++ b/src/display/mapcanvas.h @@ -30,7 +30,7 @@ #include #include #include -#include +#include #include class CharacterBatch; @@ -47,7 +47,7 @@ class QWheelEvent; class QWidget; class RoomSelFakeGL; -class NODISCARD_QOBJECT MapCanvas final : public QOpenGLWidget, +class NODISCARD_QOBJECT MapCanvas final : public QOpenGLWindow, private MapCanvasViewport, private MapCanvasInputState { @@ -152,11 +152,23 @@ class NODISCARD_QOBJECT MapCanvas final : public QOpenGLWidget, }; std::optional m_altDragState; + struct DragState + { + glm::vec3 startWorldPos; + glm::vec2 startScroll; + glm::mat4 startViewProj; + }; + std::optional m_dragState; + + float m_initialPinchDistance = 0.f; + float m_lastPinchFactor = 1.f; + float m_lastMagnification = 1.f; + public: explicit MapCanvas(MapData &mapData, PrespammedPath &prespammedPath, Mmapper2Group &groupManager, - QWidget *parent); + QWindow *parent = nullptr); ~MapCanvas() final; public: @@ -168,9 +180,6 @@ class NODISCARD_QOBJECT MapCanvas final : public QOpenGLWidget, void cleanupOpenGL(); public: - NODISCARD QSize minimumSizeHint() const override; - NODISCARD QSize sizeHint() const override; - using MapCanvasViewport::getTotalScaleFactor; void setZoom(float zoom) { @@ -180,12 +189,14 @@ class NODISCARD_QOBJECT MapCanvas final : public QOpenGLWidget, NODISCARD float getRawZoom() const { return m_scaleFactor.getRaw(); } public: - NODISCARD auto width() const { return QOpenGLWidget::width(); } - NODISCARD auto height() const { return QOpenGLWidget::height(); } - NODISCARD auto rect() const { return QOpenGLWidget::rect(); } + NODISCARD auto width() const { return QOpenGLWindow::width(); } + NODISCARD auto height() const { return QOpenGLWindow::height(); } + NODISCARD QRect rect() const { return QRect(0, 0, width(), height()); } private: void onMovement(); + void startMoving(const MouseSel &startPos); + void stopMoving(); private: void reportGLVersion(); @@ -202,6 +213,7 @@ class NODISCARD_QOBJECT MapCanvas final : public QOpenGLWidget, void mouseReleaseEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void wheelEvent(QWheelEvent *event) override; + void touchEvent(QTouchEvent *event) override; bool event(QEvent *e) override; private: @@ -228,6 +240,9 @@ class NODISCARD_QOBJECT MapCanvas final : public QOpenGLWidget, void setMvp(const glm::mat4 &viewProj); void setViewportAndMvp(int width, int height); + void zoomAt(float factor, const glm::vec2 &mousePos); + void handleZoomAtEvent(const QInputEvent *event, float deltaFactor); + NODISCARD BatchedInfomarksMeshes getInfomarksMeshes(); void drawInfomark(InfomarksBatch &batch, const InfomarkHandle &marker, @@ -288,6 +303,10 @@ class NODISCARD_QOBJECT MapCanvas final : public QOpenGLWidget, void sig_setCurrentRoom(RoomId id, bool update); void sig_zoomChanged(float); + void sig_showTooltip(const QString &text, const QPoint &pos); + void sig_customContextMenuRequested(const QPoint &pos); + void sig_dismissContextMenu(); + public slots: void slot_onForcedPositionChange(); void slot_createRoom(); diff --git a/src/display/mapcanvas_gl.cpp b/src/display/mapcanvas_gl.cpp index fa619578f..2a4744c80 100644 --- a/src/display/mapcanvas_gl.cpp +++ b/src/display/mapcanvas_gl.cpp @@ -46,9 +46,10 @@ #include #include +#include #include #include -#include +#include #include #include #include @@ -96,15 +97,15 @@ void setShowPerfStats(const bool show) class NODISCARD MakeCurrentRaii final { private: - QOpenGLWidget &m_glWidget; + QOpenGLWindow &m_glWindow; public: - explicit MakeCurrentRaii(QOpenGLWidget &widget) - : m_glWidget{widget} + explicit MakeCurrentRaii(QOpenGLWindow &window) + : m_glWindow{window} { - m_glWidget.makeCurrent(); + m_glWindow.makeCurrent(); } - ~MakeCurrentRaii() { m_glWidget.doneCurrent(); } + ~MakeCurrentRaii() { m_glWindow.doneCurrent(); } DELETE_CTORS_AND_ASSIGN_OPS(MakeCurrentRaii); }; @@ -215,7 +216,7 @@ void MapCanvas::initializeGL() } catch (const std::exception &) { hide(); doneCurrent(); - QMessageBox::critical(this, + QMessageBox::critical(QApplication::activeWindow(), "Unable to initialize OpenGL", "Upgrade your video card drivers"); if constexpr (CURRENT_PLATFORM == PlatformEnum::Windows) { @@ -859,7 +860,7 @@ void MapCanvas::paintGL() const auto &afterBatches = optAfterBatches.value(); const auto afterPaint = Clock::now(); const bool calledFinish = std::invoke([this]() -> bool { - if (auto *const ctxt = QOpenGLWidget::context()) { + if (auto *const ctxt = QOpenGLWindow::context()) { if (auto *const func = ctxt->functions()) { func->glFinish(); return true; diff --git a/src/display/mapwindow.cpp b/src/display/mapwindow.cpp index f5e12c263..bff3a487f 100644 --- a/src/display/mapwindow.cpp +++ b/src/display/mapwindow.cpp @@ -7,8 +7,10 @@ #include "mapwindow.h" #include "../display/Filenames.h" +#include "../global/MakeQPointer.h" #include "../global/SignalBlocker.h" #include "../global/Version.h" +#include "../global/utils.h" #include "mapcanvas.h" #include @@ -17,37 +19,43 @@ #include #include #include +#include class QResizeEvent; MapWindow::MapWindow(MapData &mapData, PrespammedPath &pp, Mmapper2Group &gm, QWidget *const parent) : QWidget(parent) { - m_gridLayout = std::make_unique(this); + m_gridLayout = mmqt::makeQPointer(this); m_gridLayout->setSpacing(0); m_gridLayout->setContentsMargins(0, 0, 0, 0); - m_verticalScrollBar = std::make_unique(this); + m_verticalScrollBar = mmqt::makeQPointer(this); m_verticalScrollBar->setOrientation(Qt::Vertical); m_verticalScrollBar->setRange(0, 0); m_verticalScrollBar->hide(); m_verticalScrollBar->setSingleStep(MapCanvas::SCROLL_SCALE); - m_gridLayout->addWidget(m_verticalScrollBar.get(), 0, 1, 1, 1); + m_gridLayout->addWidget(m_verticalScrollBar, 0, 1, 1, 1); - m_horizontalScrollBar = std::make_unique(this); + m_horizontalScrollBar = mmqt::makeQPointer(this); m_horizontalScrollBar->setOrientation(Qt::Horizontal); m_horizontalScrollBar->setRange(0, 0); m_horizontalScrollBar->hide(); m_horizontalScrollBar->setSingleStep(MapCanvas::SCROLL_SCALE); - m_gridLayout->addWidget(m_horizontalScrollBar.get(), 1, 0, 1, 1); + m_gridLayout->addWidget(m_horizontalScrollBar, 1, 0, 1, 1); - m_canvas = std::make_unique(mapData, pp, gm, this); - MapCanvas *const canvas = m_canvas.get(); + m_canvas = new MapCanvas(mapData, pp, gm); + m_canvas->setMinimumSize(QSize(1280 / 4, 720 / 4)); + m_canvas->resize(QSize(1280, 720)); - m_gridLayout->addWidget(canvas, 0, 0, 1, 1); - setMinimumSize(canvas->minimumSizeHint()); + m_canvasContainer = QWidget::createWindowContainer(m_canvas, this); + assert(m_canvasContainer); + assert(m_canvasContainer->parent() == this); + + m_gridLayout->addWidget(m_canvasContainer, 0, 0, 1, 1); + setMinimumSize(m_canvas->minimumSize()); // Splash setup auto createSplashPixmap = [](const QSize &targetLogicalSize, qreal dpr) -> QPixmap { @@ -89,56 +97,60 @@ MapWindow::MapWindow(MapData &mapData, PrespammedPath &pp, Mmapper2Group &gm, QW auto splashPixmap = createSplashPixmap(size(), devicePixelRatioF()); // Now set pixmap with painted text - m_splashLabel = std::make_unique(this); + m_splashLabel = mmqt::makeQPointer(this); m_splashLabel->setPixmap(splashPixmap); m_splashLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_splashLabel->setGeometry(rect()); - m_gridLayout->addWidget(m_splashLabel.get(), 0, 0, 1, 1, Qt::AlignCenter); + m_gridLayout->addWidget(m_splashLabel, 0, 0, 1, 1, Qt::AlignCenter); m_splashLabel->show(); // from map window to canvas { - connect(m_horizontalScrollBar.get(), + connect(m_horizontalScrollBar, &QScrollBar::valueChanged, - canvas, + m_canvas, [this](const int x) -> void { const float val = m_knownMapSize.scrollToWorld(glm::ivec2{x, 0}).x; m_canvas->slot_setHorizontalScroll(val); }); - connect(m_verticalScrollBar.get(), + connect(m_verticalScrollBar, &QScrollBar::valueChanged, - canvas, + m_canvas, [this](const int y) -> void { const float value = m_knownMapSize.scrollToWorld(glm::ivec2{0, y}).y; m_canvas->slot_setVerticalScroll(value); }); - connect(this, &MapWindow::sig_setScroll, canvas, &MapCanvas::slot_setScroll); + connect(this, &MapWindow::sig_setScroll, m_canvas, &MapCanvas::slot_setScroll); } // from canvas to map window { - connect(canvas, &MapCanvas::sig_onCenter, this, &MapWindow::slot_centerOnWorldPos); - connect(canvas, &MapCanvas::sig_setScrollBars, this, &MapWindow::slot_setScrollBars); - connect(canvas, &MapCanvas::sig_continuousScroll, this, &MapWindow::slot_continuousScroll); - connect(canvas, &MapCanvas::sig_mapMove, this, &MapWindow::slot_mapMove); - connect(canvas, &MapCanvas::sig_zoomChanged, this, &MapWindow::slot_zoomChanged); + connect(m_canvas, &MapCanvas::sig_onCenter, this, &MapWindow::slot_centerOnWorldPos); + connect(m_canvas, &MapCanvas::sig_setScrollBars, this, &MapWindow::slot_setScrollBars); + connect(m_canvas, &MapCanvas::sig_continuousScroll, this, &MapWindow::slot_continuousScroll); + connect(m_canvas, &MapCanvas::sig_mapMove, this, &MapWindow::slot_mapMove); + connect(m_canvas, &MapCanvas::sig_zoomChanged, this, &MapWindow::slot_zoomChanged); + connect(m_canvas, &MapCanvas::sig_showTooltip, this, &MapWindow::slot_showTooltip); } + + m_scrollTimer = mmqt::makeQPointer(this); + connect(m_scrollTimer, &QTimer::timeout, this, &MapWindow::slot_scrollTimerTimeout); } void MapWindow::hideSplashImage() { if (m_splashLabel) { m_splashLabel->hide(); - m_splashLabel.release(); + m_splashLabel->deleteLater(); } } void MapWindow::keyPressEvent(QKeyEvent *const event) { if (event->key() == Qt::Key_Escape) { - m_canvas->userPressedEscape(true); + deref(m_canvas).userPressedEscape(true); return; } QWidget::keyPressEvent(event); @@ -147,7 +159,7 @@ void MapWindow::keyPressEvent(QKeyEvent *const event) void MapWindow::keyReleaseEvent(QKeyEvent *const event) { if (event->key() == Qt::Key_Escape) { - m_canvas->userPressedEscape(false); + deref(m_canvas).userPressedEscape(false); return; } QWidget::keyReleaseEvent(event); @@ -157,14 +169,16 @@ MapWindow::~MapWindow() = default; void MapWindow::slot_mapMove(const int dx, const int input_dy) { + auto &horz = deref(m_horizontalScrollBar); + auto &vert = deref(m_verticalScrollBar); + const SignalBlocker block_horz{horz}; + const SignalBlocker block_vert{vert}; + // Y is negated because delta is in world space const int dy = -input_dy; - const SignalBlocker block_horz{*m_horizontalScrollBar}; - const SignalBlocker block_vert{*m_verticalScrollBar}; - - const int hValue = m_horizontalScrollBar->value() + dx; - const int vValue = m_verticalScrollBar->value() + dy; + const int hValue = horz.value() + dx; + const int vValue = vert.value() + dy; const glm::ivec2 scrollPos{hValue, vValue}; centerOnScrollPos(scrollPos); @@ -188,29 +202,29 @@ void MapWindow::slot_continuousScroll(const int hStep, const int input_vStep) m_horizontalScrollStep = hStep; m_verticalScrollStep = vStep; + auto &scrollTimer = deref(m_scrollTimer); // stop - if ((scrollTimer != nullptr) && hStep == 0 && vStep == 0) { - if (scrollTimer->isActive()) { - scrollTimer->stop(); + if (hStep == 0 && vStep == 0) { + if (scrollTimer.isActive()) { + scrollTimer.stop(); + } + } else { + // start + if (!scrollTimer.isActive()) { + scrollTimer.start(100); } - scrollTimer.reset(); - } - - // start - if ((scrollTimer == nullptr) && (hStep != 0 || vStep != 0)) { - scrollTimer = std::make_unique(this); - connect(scrollTimer.get(), &QTimer::timeout, this, &MapWindow::slot_scrollTimerTimeout); - scrollTimer->start(100); } } void MapWindow::slot_scrollTimerTimeout() { - const SignalBlocker block_horz{*m_horizontalScrollBar}; - const SignalBlocker block_vert{*m_verticalScrollBar}; + auto &horz = deref(m_horizontalScrollBar); + auto &vert = deref(m_verticalScrollBar); + const SignalBlocker block_horz{horz}; + const SignalBlocker block_vert{vert}; - const int vValue = m_verticalScrollBar->value() + m_verticalScrollStep; - const int hValue = m_horizontalScrollBar->value() + m_horizontalScrollStep; + const int vValue = vert.value() + m_verticalScrollStep; + const int hValue = horz.value() + m_horizontalScrollStep; const glm::ivec2 scrollPos{hValue, vValue}; centerOnScrollPos(scrollPos); @@ -218,19 +232,25 @@ void MapWindow::slot_scrollTimerTimeout() void MapWindow::slot_graphicsSettingsChanged() { - this->m_canvas->graphicsSettingsChanged(); + deref(m_canvas).graphicsSettingsChanged(); } void MapWindow::slot_centerOnWorldPos(const glm::vec2 &worldPos) { + auto &horz = deref(m_horizontalScrollBar); + auto &vert = deref(m_verticalScrollBar); + const SignalBlocker block_horz{horz}; + const SignalBlocker block_vert{vert}; + const auto scrollPos = m_knownMapSize.worldToScroll(worldPos); - centerOnScrollPos(scrollPos); + horz.setValue(scrollPos.x); + vert.setValue(scrollPos.y); } void MapWindow::centerOnScrollPos(const glm::ivec2 &scrollPos) { - m_horizontalScrollBar->setValue(scrollPos.x); - m_verticalScrollBar->setValue(scrollPos.y); + deref(m_horizontalScrollBar).setValue(scrollPos.x); + deref(m_verticalScrollBar).setValue(scrollPos.y); const auto worldPos = m_knownMapSize.scrollToWorld(scrollPos); emit sig_setScroll(worldPos); @@ -252,34 +272,48 @@ void MapWindow::updateScrollBars() { const auto dims = m_knownMapSize.size() * MapCanvas::SCROLL_SCALE; const auto showScrollBars = getConfig().general.showScrollBars; - m_horizontalScrollBar->setRange(0, dims.x); + + auto &horz = deref(m_horizontalScrollBar); + horz.setRange(0, dims.x); if (dims.x > 0 && showScrollBars) { - m_horizontalScrollBar->show(); + horz.show(); } else { - m_horizontalScrollBar->hide(); + horz.hide(); } - m_verticalScrollBar->setRange(0, dims.y); + auto &vert = deref(m_verticalScrollBar); + vert.setRange(0, dims.y); if (dims.y > 0 && showScrollBars) { - m_verticalScrollBar->show(); + vert.show(); } else { - m_verticalScrollBar->hide(); + vert.hide(); } } MapCanvas *MapWindow::getCanvas() const { - return m_canvas.get(); + return m_canvas; } void MapWindow::setZoom(const float zoom) { - m_canvas->setZoom(zoom); + deref(m_canvas).setZoom(zoom); } float MapWindow::getZoom() const { - return m_canvas->getRawZoom(); + return deref(m_canvas).getRawZoom(); +} + +void MapWindow::slot_showTooltip(const QString &text, const QPoint &pos) +{ + auto &container = deref(m_canvasContainer); + QToolTip::showText(container.mapToGlobal(pos), text, &container, container.rect(), 5000); +} + +void MapWindow::setCanvasEnabled(bool enabled) +{ + deref(m_canvasContainer).setEnabled(enabled); } glm::vec2 MapWindow::KnownMapSize::scrollToWorld(const glm::ivec2 &scrollPos) const diff --git a/src/display/mapwindow.h b/src/display/mapwindow.h index 1f9f76600..49cc6d9bc 100644 --- a/src/display/mapwindow.h +++ b/src/display/mapwindow.h @@ -8,11 +8,10 @@ #include "../map/coordinate.h" #include "mapcanvas.h" -#include - #include #include #include +#include #include #include #include @@ -36,16 +35,16 @@ class NODISCARD_QOBJECT MapWindow final : public QWidget Q_OBJECT protected: - std::unique_ptr scrollTimer; + QPointer m_gridLayout; + QPointer m_horizontalScrollBar; + QPointer m_verticalScrollBar; + QPointer m_canvas; + QPointer m_canvasContainer; + QPointer m_splashLabel; + QPointer m_scrollTimer; int m_verticalScrollStep = 0; int m_horizontalScrollStep = 0; - std::unique_ptr m_gridLayout; - std::unique_ptr m_horizontalScrollBar; - std::unique_ptr m_verticalScrollBar; - std::unique_ptr m_canvas; - std::unique_ptr m_splashLabel; - private: struct NODISCARD KnownMapSize final { @@ -91,4 +90,7 @@ public slots: void slot_scrollTimerTimeout(); void slot_graphicsSettingsChanged(); void slot_zoomChanged(const float zoom) { emit sig_zoomChanged(zoom); } + void slot_showTooltip(const QString &text, const QPoint &pos); + + void setCanvasEnabled(bool enabled); }; diff --git a/src/mainwindow/mainwindow.cpp b/src/mainwindow/mainwindow.cpp index 00392fb0c..93a47f567 100644 --- a/src/mainwindow/mainwindow.cpp +++ b/src/mainwindow/mainwindow.cpp @@ -465,7 +465,11 @@ void MainWindow::wireConnections() &MapCanvas::sig_newInfomarkSelection, this, &MainWindow::slot_newInfomarkSelection); - connect(canvas, &QWidget::customContextMenuRequested, this, &MainWindow::slot_showContextMenu); + connect(canvas, + &MapCanvas::sig_customContextMenuRequested, + this, + &MainWindow::slot_showContextMenu); + connect(canvas, &MapCanvas::sig_dismissContextMenu, this, &MainWindow::slot_closeContextMenu); // Group connect(m_groupManager, &Mmapper2Group::sig_log, this, &MainWindow::slot_log); @@ -1224,46 +1228,51 @@ void MainWindow::setupMenuBar() mumeMenu->addAction(mumeWikiAct); helpMenu->addSeparator(); helpMenu->addAction(aboutAct); - helpMenu->addAction(aboutQtAct); + if constexpr (CURRENT_PLATFORM != PlatformEnum::Wasm) { + helpMenu->addAction(aboutQtAct); + } } void MainWindow::slot_showContextMenu(const QPoint &pos) { - auto *contextMenu = new QMenu(tr("Context menu"), this); - contextMenu->setAttribute(Qt::WA_DeleteOnClose); + slot_closeContextMenu(); + + m_contextMenu = new QMenu(tr("Context menu"), this); + auto &contextMenu = deref(m_contextMenu); + contextMenu.setAttribute(Qt::WA_DeleteOnClose); if (m_connectionSelection != nullptr) { // Connections cannot be selected alongside rooms and infomarks // ^^^ Let's enforce that with a variant then? - contextMenu->addAction(deleteConnectionSelectionAct); + contextMenu.addAction(deleteConnectionSelectionAct); } else { // However, both rooms and infomarks can be selected at once if (m_roomSelection != nullptr) { if (m_roomSelection->empty()) { - contextMenu->addAction(createRoomAct); + contextMenu.addAction(createRoomAct); } else { - contextMenu->addAction(editRoomSelectionAct); - contextMenu->addAction(moveUpRoomSelectionAct); - contextMenu->addAction(moveDownRoomSelectionAct); - contextMenu->addAction(mergeUpRoomSelectionAct); - contextMenu->addAction(mergeDownRoomSelectionAct); - contextMenu->addAction(deleteRoomSelectionAct); - contextMenu->addAction(connectToNeighboursRoomSelectionAct); - contextMenu->addSeparator(); - contextMenu->addAction(gotoRoomAct); - contextMenu->addAction(forceRoomAct); + contextMenu.addAction(editRoomSelectionAct); + contextMenu.addAction(moveUpRoomSelectionAct); + contextMenu.addAction(moveDownRoomSelectionAct); + contextMenu.addAction(mergeUpRoomSelectionAct); + contextMenu.addAction(mergeDownRoomSelectionAct); + contextMenu.addAction(deleteRoomSelectionAct); + contextMenu.addAction(connectToNeighboursRoomSelectionAct); + contextMenu.addSeparator(); + contextMenu.addAction(gotoRoomAct); + contextMenu.addAction(forceRoomAct); } } if (m_infoMarkSelection != nullptr && !m_infoMarkSelection->empty()) { if (m_roomSelection != nullptr) { - contextMenu->addSeparator(); + contextMenu.addSeparator(); } - contextMenu->addAction(infomarkActions.editInfomarkAct); - contextMenu->addAction(infomarkActions.deleteInfomarkAct); + contextMenu.addAction(infomarkActions.editInfomarkAct); + contextMenu.addAction(infomarkActions.deleteInfomarkAct); } } - contextMenu->addSeparator(); - QMenu *mouseMenu = contextMenu->addMenu(QIcon::fromTheme("input-mouse"), "Mouse Mode"); + contextMenu.addSeparator(); + QMenu *mouseMenu = contextMenu.addMenu(QIcon::fromTheme("input-mouse"), "Mouse Mode"); mouseMenu->addAction(mouseMode.modeMoveSelectAct); mouseMenu->addAction(mouseMode.modeRoomRaypickAct); mouseMenu->addAction(mouseMode.modeRoomSelectAct); @@ -1274,7 +1283,14 @@ void MainWindow::slot_showContextMenu(const QPoint &pos) mouseMenu->addAction(mouseMode.modeCreateConnectionAct); mouseMenu->addAction(mouseMode.modeCreateOnewayConnectionAct); - contextMenu->popup(getCanvas()->mapToGlobal(pos)); + contextMenu.popup(getCanvas()->mapToGlobal(pos)); +} + +void MainWindow::slot_closeContextMenu() +{ + if (m_contextMenu) { + m_contextMenu->close(); + } } void MainWindow::slot_alwaysOnTop() @@ -1313,7 +1329,6 @@ void MainWindow::slot_setShowMenuBar() m_dockDialogGroup->setMouseTracking(!showMenuBar); m_dockDialogLog->setMouseTracking(!showMenuBar); m_dockDialogRoom->setMouseTracking(!showMenuBar); - getCanvas()->setMouseTracking(!showMenuBar); if (showMenuBar) { menuBar()->show(); @@ -1322,14 +1337,16 @@ void MainWindow::slot_setShowMenuBar() m_dockDialogGroup->removeEventFilter(this); m_dockDialogLog->removeEventFilter(this); m_dockDialogRoom->removeEventFilter(this); - getCanvas()->removeEventFilter(this); + m_mapWindow->removeEventFilter(this); + m_mapWindow->getCanvas()->removeEventFilter(this); } else { m_dockDialogAdventure->installEventFilter(this); m_dockDialogClient->installEventFilter(this); m_dockDialogGroup->installEventFilter(this); m_dockDialogLog->installEventFilter(this); m_dockDialogRoom->installEventFilter(this); - getCanvas()->installEventFilter(this); + m_mapWindow->installEventFilter(this); + m_mapWindow->getCanvas()->installEventFilter(this); } } diff --git a/src/mainwindow/mainwindow.h b/src/mainwindow/mainwindow.h index 18d16eda8..b7765a2a6 100644 --- a/src/mainwindow/mainwindow.h +++ b/src/mainwindow/mainwindow.h @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include #include @@ -112,6 +114,8 @@ class NODISCARD_QOBJECT MainWindow final : public QMainWindow DescriptionWidget *m_descriptionWidget = nullptr; std::unique_ptr m_hotkeyManager; + QPointer m_contextMenu; + SharedRoomSelection m_roomSelection; std::shared_ptr m_connectionSelection; std::shared_ptr m_infoMarkSelection; @@ -438,6 +442,7 @@ public slots: void slot_newConnectionSelection(ConnectionSelection *); void slot_newInfomarkSelection(InfomarkSelection *); void slot_showContextMenu(const QPoint &); + void slot_closeContextMenu(); void slot_onCheckForUpdate(); void slot_voteForMUME(); diff --git a/src/mainwindow/utils.cpp b/src/mainwindow/utils.cpp index 41b911b42..92e3f9532 100644 --- a/src/mainwindow/utils.cpp +++ b/src/mainwindow/utils.cpp @@ -9,12 +9,12 @@ CanvasDisabler::CanvasDisabler(MapWindow &in_window) : window{in_window} { - window.getCanvas()->setEnabled(false); + window.setCanvasEnabled(false); } CanvasDisabler::~CanvasDisabler() { - window.getCanvas()->setEnabled(true); + window.setCanvasEnabled(true); window.hideSplashImage(); }