diff --git a/src/config/ConfigDescriptions.hpp b/src/config/ConfigDescriptions.hpp index 85655dfd8..38bb0a20c 100644 --- a/src/config/ConfigDescriptions.hpp +++ b/src/config/ConfigDescriptions.hpp @@ -1658,6 +1658,12 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SBoolData{false}, }, + SConfigOptionDescription{ + .value = "cursor:zoom_detached_camera", + .description = "Detaches the camera from the mouse when zoomed in, only ever moving to keep the mouse in view", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, SConfigOptionDescription{ .value = "cursor:enable_hyprcursor", .description = "whether to enable hyprcursor support", diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index f8acb4738..94147f499 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -734,6 +734,7 @@ CConfigManager::CConfigManager() { registerConfigVar("cursor:zoom_factor", {1.f}); registerConfigVar("cursor:zoom_rigid", Hyprlang::INT{0}); registerConfigVar("cursor:zoom_disable_aa", Hyprlang::INT{0}); + registerConfigVar("cursor:zoom_detached_camera", Hyprlang::INT{1}); registerConfigVar("cursor:enable_hyprcursor", Hyprlang::INT{1}); registerConfigVar("cursor:sync_gsettings_theme", Hyprlang::INT{1}); registerConfigVar("cursor:hide_on_key_press", Hyprlang::INT{0}); diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index debf2ec74..95e5ce5cb 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -11,6 +11,7 @@ #include "CMType.hpp" #include +#include "MonitorZoomController.hpp" #include "time/Timer.hpp" #include "math/Math.hpp" #include "../desktop/reserved/ReservedArea.hpp" @@ -130,6 +131,8 @@ class CMonitor { uint32_t m_drmFormat = DRM_FORMAT_INVALID; uint32_t m_prevDrmFormat = DRM_FORMAT_INVALID; + CMonitorZoomController m_zoomController; + bool m_dpmsStatus = true; bool m_vrrActive = false; // this can be TRUE even if VRR is not active in the case that this display does not support it. bool m_enabled10bit = false; // as above, this can be TRUE even if 10 bit failed. diff --git a/src/helpers/MonitorZoomController.cpp b/src/helpers/MonitorZoomController.cpp new file mode 100644 index 000000000..d90f416fe --- /dev/null +++ b/src/helpers/MonitorZoomController.cpp @@ -0,0 +1,97 @@ +#include "MonitorZoomController.hpp" + +#include +#include "../config/ConfigValue.hpp" +#include "../managers/input/InputManager.hpp" +#include "../render/OpenGL.hpp" +#include "desktop/DesktopTypes.hpp" +#include "render/Renderer.hpp" + +void CMonitorZoomController::zoomWithDetachedCamera(CBox& result, const SCurrentRenderData& m_renderData) { + const auto m = m_renderData.pMonitor; + auto monbox = CBox(0, 0, m->m_size.x, m->m_size.y); + const auto ZOOM = m_renderData.mouseZoomFactor; + const auto MOUSE = g_pInputManager->getMouseCoordsInternal() - m->m_position; + + if (m_lastZoomLevel != ZOOM) { + if (m_resetCameraState) { + m_resetCameraState = false; + m_camera = CBox(0, 0, m->m_size.x, m->m_size.y); + m_lastZoomLevel = 1.0f; + } + const CBox old = m_camera; + + // mouse normalized inside screen (0..1) + const float mx = MOUSE.x / m->m_size.x; + const float my = MOUSE.y / m->m_size.y; + // world-space point under the cursor before zoom + const float mouseWorldX = old.x + (mx * old.w); + const float mouseWorldY = old.y + (my * old.h); + + const auto CAMERAW = monbox.w / ZOOM; + const auto CAMERAH = monbox.h / ZOOM; + + // compute new top-left so the same world point stays under the cursor + const float newX = mouseWorldX - (mx * CAMERAW); + const float newY = mouseWorldY - (my * CAMERAH); + + m_camera = CBox(newX, newY, CAMERAW, CAMERAH); + // Detect if this zoom would've caused jerk to keep mouse in view and disable edges if so + if (!m_camera.copy().scaleFromCenter(.9).containsPoint(MOUSE)) + m_padCamEdges = false; + m_lastZoomLevel = ZOOM; + } + + // Keep mouse inside cameraview + auto smallerbox = m_camera; + // Prevent zoom step from causing us to jerk to keep mouse in padded camera view, + // but let us switch to the padded camera once the mouse moves into the safe area + if (!m_padCamEdges) + if (smallerbox.copy().scaleFromCenter(.9).containsPoint(MOUSE)) + m_padCamEdges = true; + if (m_padCamEdges) + smallerbox.scaleFromCenter(.9); + if (!smallerbox.containsPoint(MOUSE)) { + if (MOUSE.x < smallerbox.x) + m_camera.x -= smallerbox.x - MOUSE.x; + if (MOUSE.y < smallerbox.y) + m_camera.y -= smallerbox.y - MOUSE.y; + if (MOUSE.y > smallerbox.y + smallerbox.h) + m_camera.y += MOUSE.y - (smallerbox.y + smallerbox.h); + if (MOUSE.x > smallerbox.x + smallerbox.w) + m_camera.x += MOUSE.x - (smallerbox.x + smallerbox.w); + } + + auto z = ZOOM * m->m_scale; + monbox.scale(z).translate(-m_camera.pos() * z); + + result = monbox; +} + +void CMonitorZoomController::applyZoomTransform(CBox& monbox, const SCurrentRenderData& m_renderData) { + static auto PZOOMRIGID = CConfigValue("cursor:zoom_rigid"); + static auto PZOOMDETACHEDCAMERA = CConfigValue("cursor:zoom_detached_camera"); + const auto ZOOM = m_renderData.mouseZoomFactor; + + if (ZOOM == 1.0f) + return; + + const auto m = m_renderData.pMonitor; + const auto ORIGINAL = monbox; + const auto INITANIM = m->m_zoomAnimProgress->value() != 1.0; + + if (*PZOOMDETACHEDCAMERA && !INITANIM) + zoomWithDetachedCamera(monbox, m_renderData); + else { + const auto ZOOMCENTER = m_renderData.mouseZoomUseMouse ? (g_pInputManager->getMouseCoordsInternal() - m->m_position) * m->m_scale : m->m_transformedSize / 2.f; + + monbox.translate(-ZOOMCENTER).scale(ZOOM).translate(*PZOOMRIGID ? m->m_transformedSize / 2.0 : ZOOMCENTER); + } + + monbox.x = std::min(monbox.x, 0.0); + monbox.y = std::min(monbox.y, 0.0); + if (monbox.x + monbox.width < ORIGINAL.w) + monbox.x = ORIGINAL.w - monbox.width; + if (monbox.y + monbox.height < ORIGINAL.h) + monbox.y = ORIGINAL.h - monbox.height; +} diff --git a/src/helpers/MonitorZoomController.hpp b/src/helpers/MonitorZoomController.hpp new file mode 100644 index 000000000..4f7c9d7a2 --- /dev/null +++ b/src/helpers/MonitorZoomController.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "./math/Math.hpp" + +struct SCurrentRenderData; + +class CMonitorZoomController { + public: + bool m_resetCameraState = true; + + void applyZoomTransform(CBox& monbox, const SCurrentRenderData& m_renderData); + + private: + void zoomWithDetachedCamera(CBox& result, const SCurrentRenderData& m_renderData); + + CBox m_camera; + float m_lastZoomLevel = 1.0f; + bool m_padCamEdges = true; +}; diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 6b5e35f1d..198ba0e4a 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -849,7 +849,6 @@ void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, CFrameb } void CHyprOpenGLImpl::end() { - static auto PZOOMRIGID = CConfigValue("cursor:zoom_rigid"); static auto PZOOMDISABLEAA = CConfigValue("cursor:zoom_disable_aa"); TRACY_GPU_ZONE("RenderEnd"); @@ -861,20 +860,9 @@ void CHyprOpenGLImpl::end() { CBox monbox = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; - if (m_renderData.mouseZoomFactor != 1.f) { - const auto ZOOMCENTER = m_renderData.mouseZoomUseMouse ? - (g_pInputManager->getMouseCoordsInternal() - m_renderData.pMonitor->m_position) * m_renderData.pMonitor->m_scale : - m_renderData.pMonitor->m_transformedSize / 2.f; - - monbox.translate(-ZOOMCENTER).scale(m_renderData.mouseZoomFactor).translate(*PZOOMRIGID ? m_renderData.pMonitor->m_transformedSize / 2.0 : ZOOMCENTER); - - monbox.x = std::min(monbox.x, 0.0); - monbox.y = std::min(monbox.y, 0.0); - if (monbox.x + monbox.width < m_renderData.pMonitor->m_transformedSize.x) - monbox.x = m_renderData.pMonitor->m_transformedSize.x - monbox.width; - if (monbox.y + monbox.height < m_renderData.pMonitor->m_transformedSize.y) - monbox.y = m_renderData.pMonitor->m_transformedSize.y - monbox.height; - } + if (g_pHyprRenderer->m_renderMode == RENDER_MODE_NORMAL && m_renderData.mouseZoomFactor == 1.0f) + m_renderData.pMonitor->m_zoomController.m_resetCameraState = true; + m_renderData.pMonitor->m_zoomController.applyZoomTransform(monbox, m_renderData); m_applyFinalShader = !m_renderData.blockScreenShader; if (m_renderData.mouseZoomUseMouse && *PZOOMDISABLEAA)