summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorjmanc3 <jman12316@live.com>2025-12-14 13:42:02 -0600
committerGitHub <noreply@github.com>2025-12-14 19:42:02 +0000
commite4a8f2b14f789075612bfd26e8153039c8f295f2 (patch)
treeb7119ab4adbd4eefb7d23413ca5d35637f7e8bf6 /src
parent6535ff07c9f16dbd4928f1ef8a12a939db59f7b5 (diff)
renderer: add zoom with detached camera (#12548)
Diffstat (limited to 'src')
-rw-r--r--src/config/ConfigDescriptions.hpp6
-rw-r--r--src/config/ConfigManager.cpp1
-rw-r--r--src/helpers/Monitor.hpp3
-rw-r--r--src/helpers/MonitorZoomController.cpp97
-rw-r--r--src/helpers/MonitorZoomController.hpp19
-rw-r--r--src/render/OpenGL.cpp18
6 files changed, 129 insertions, 15 deletions
diff --git a/src/config/ConfigDescriptions.hpp b/src/config/ConfigDescriptions.hpp
index 85655dfd..38bb0a20 100644
--- a/src/config/ConfigDescriptions.hpp
+++ b/src/config/ConfigDescriptions.hpp
@@ -1659,6 +1659,12 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.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",
.type = CONFIG_OPTION_BOOL,
diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp
index f8acb473..94147f49 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 debf2ec7..95e5ce5c 100644
--- a/src/helpers/Monitor.hpp
+++ b/src/helpers/Monitor.hpp
@@ -11,6 +11,7 @@
#include "CMType.hpp"
#include <xf86drmMode.h>
+#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 00000000..d90f416f
--- /dev/null
+++ b/src/helpers/MonitorZoomController.cpp
@@ -0,0 +1,97 @@
+#include "MonitorZoomController.hpp"
+
+#include <hyprlang.hpp>
+#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<Hyprlang::INT>("cursor:zoom_rigid");
+ static auto PZOOMDETACHEDCAMERA = CConfigValue<Hyprlang::INT>("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 00000000..4f7c9d7a
--- /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 6b5e35f1..198ba0e4 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<Hyprlang::INT>("cursor:zoom_rigid");
static auto PZOOMDISABLEAA = CConfigValue<Hyprlang::INT>("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)