diff --git a/kwin/src/breezydesktopconfig.kcfg b/kwin/src/breezydesktopconfig.kcfg
index b2129c8..e6772ef 100644
--- a/kwin/src/breezydesktopconfig.kcfg
+++ b/kwin/src/breezydesktopconfig.kcfg
@@ -5,11 +5,17 @@
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
-
+
+ 0.85
+ 0.2
+ 2.5
+
+
+
1.05
0.2
2.5
-
+
diff --git a/kwin/src/breezydesktopeffect.cpp b/kwin/src/breezydesktopeffect.cpp
index 6789161..81b056c 100644
--- a/kwin/src/breezydesktopeffect.cpp
+++ b/kwin/src/breezydesktopeffect.cpp
@@ -123,7 +123,8 @@ BreezyDesktopEffect::BreezyDesktopEffect()
void BreezyDesktopEffect::reconfigure(ReconfigureFlags)
{
BreezyDesktopConfig::self()->read();
- setDisplayDistance(BreezyDesktopConfig::displayDistance());
+ setFocusedDisplayDistance(BreezyDesktopConfig::focusedDisplayDistance());
+ setAllDisplaysDistance(BreezyDesktopConfig::allDisplaysDistance());
}
QVariantMap BreezyDesktopEffect::initialProperties(Output *screen)
@@ -205,13 +206,24 @@ QList BreezyDesktopEffect::displayResolution() const {
return m_displayResolution;
}
-qreal BreezyDesktopEffect::displayDistance() const {
- return m_displayDistance;
+qreal BreezyDesktopEffect::focusedDisplayDistance() const {
+ return m_focusedDisplayDistance;
}
-void BreezyDesktopEffect::setDisplayDistance(qreal distance) {
- if (distance != m_displayDistance) {
- m_displayDistance = std::clamp(distance, 0.2, 2.5);
+void BreezyDesktopEffect::setFocusedDisplayDistance(qreal distance) {
+ if (distance != m_focusedDisplayDistance) {
+ m_focusedDisplayDistance = std::clamp(distance, 0.2, 2.5);
+ Q_EMIT displayDistanceChanged();
+ }
+}
+
+qreal BreezyDesktopEffect::allDisplaysDistance() const {
+ return m_allDisplaysDistance;
+}
+
+void BreezyDesktopEffect::setAllDisplaysDistance(qreal distance) {
+ if (distance != m_allDisplaysDistance) {
+ m_allDisplaysDistance = std::clamp(distance, 0.2, 2.5);
Q_EMIT displayDistanceChanged();
}
}
diff --git a/kwin/src/breezydesktopeffect.h b/kwin/src/breezydesktopeffect.h
index d77df4d..fd208cc 100644
--- a/kwin/src/breezydesktopeffect.h
+++ b/kwin/src/breezydesktopeffect.h
@@ -22,7 +22,8 @@ namespace KWin
Q_PROPERTY(QPointF cursorPos READ cursorPos NOTIFY cursorPosChanged)
Q_PROPERTY(QList lookAheadConfig READ lookAheadConfig NOTIFY devicePropertiesChanged)
Q_PROPERTY(QList displayResolution READ displayResolution NOTIFY devicePropertiesChanged)
- Q_PROPERTY(qreal displayDistance READ displayDistance NOTIFY displayDistanceChanged)
+ Q_PROPERTY(qreal focusedDisplayDistance READ focusedDisplayDistance NOTIFY displayDistanceChanged)
+ Q_PROPERTY(qreal allDisplaysDistance READ allDisplaysDistance NOTIFY displayDistanceChanged)
Q_PROPERTY(qreal diagonalFOV READ diagonalFOV NOTIFY devicePropertiesChanged)
Q_PROPERTY(qreal lensDistanceRatio READ lensDistanceRatio NOTIFY devicePropertiesChanged)
Q_PROPERTY(bool sbsEnabled READ sbsEnabled NOTIFY devicePropertiesChanged)
@@ -46,8 +47,10 @@ namespace KWin
bool imuResetState() const;
QList lookAheadConfig() const;
QList displayResolution() const;
- qreal displayDistance() const;
- void setDisplayDistance(qreal distance);
+ qreal focusedDisplayDistance() const;
+ void setFocusedDisplayDistance(qreal distance);
+ qreal allDisplaysDistance() const;
+ void setAllDisplaysDistance(qreal distance);
qreal diagonalFOV() const;
qreal lensDistanceRatio() const;
bool sbsEnabled() const;
@@ -101,7 +104,8 @@ namespace KWin
QFileSystemWatcher *m_shmDirectoryWatcher = nullptr;
QPointF m_cursorPos;
QTimer *m_cursorUpdateTimer = nullptr;
- qreal m_displayDistance = 1.05;
+ qreal m_focusedDisplayDistance = 0.85;
+ qreal m_allDisplaysDistance = 1.05;
};
} // namespace KWin
diff --git a/kwin/src/kcm/breezydesktopeffectkcm.ui b/kwin/src/kcm/breezydesktopeffectkcm.ui
index f134c1e..ac90744 100644
--- a/kwin/src/kcm/breezydesktopeffectkcm.ui
+++ b/kwin/src/kcm/breezydesktopeffectkcm.ui
@@ -23,14 +23,34 @@
-
-
+
- Display Distance:
+ Focused Display Distance:
-
-
+
+
+ 0.2
+
+
+ 2.5
+
+
+ 0.01
+
+
+
+ -
+
+
+ All Displays Distance:
+
+
+
+ -
+
0.2
diff --git a/kwin/src/qml/BreezyDesktop.qml b/kwin/src/qml/BreezyDesktop.qml
index 602f7fe..103af5a 100644
--- a/kwin/src/qml/BreezyDesktop.qml
+++ b/kwin/src/qml/BreezyDesktop.qml
@@ -3,9 +3,14 @@ import QtQuick3D
Node {
+ id: breezyDesktop
+
property var viewportResolution: effect.displayResolution
property var screens: root.screens
+ property var fovDetails: root.fovDetails
property var monitorPlacements: root.monitorPlacements
+ property var imuRotations: effect.imuRotations
+ property int focusedMonitorIndex: -1
// x value for placing the viewport in the middle of all screens
property real screensXMid: {
@@ -39,7 +44,6 @@ Node {
id: displays
}
-
Repeater3D {
model: screens.length
delegate: BreezyDesktopDisplay {
@@ -60,13 +64,40 @@ Node {
eulerRotation.y: screenRotationY
eulerRotation.x: screenRotationX
position: {
- // rotate about the Y (up) axis, to create a horizontal movement
- const transform = Qt.matrix4x4();
- transform.rotate(screenRotationY, Qt.vector3d(0, 1, 0));
- transform.rotate(screenRotationX, Qt.vector3d(1, 0, 0));
-
// camera looks along the negative Z axis
- return transform.times(Qt.vector3d(-monitorPlacement.centerNoRotate[1], monitorPlacement.centerNoRotate[2], -monitorPlacement.centerNoRotate[0]));
+ let positionVector = displays.nwuToEusVector(monitorPlacement.centerNoRotate);
+ if (focusedMonitorIndex === index) {
+ positionVector = positionVector.times(effect.focusedDisplayDistance / effect.allDisplaysDistance);
+ }
+
+ // position vector is only translated in flat directions, without rotations applied, so apply them here
+ const rotationMatrix = Qt.matrix4x4();
+
+ // only one of these should ever be non-zero, since we only rotate in the direction of the "wrap" preference
+ rotationMatrix.rotate(screenRotationY, Qt.vector3d(0, 1, 0));
+ rotationMatrix.rotate(screenRotationX, Qt.vector3d(1, 0, 0));
+
+ return rotationMatrix.times(positionVector);
+ }
+ }
+ }
+
+ FrameAnimation {
+ running: true
+ onTriggered: {
+ if (breezyDesktop.imuRotations && breezyDesktop.imuRotations.length > 0) {
+ const focusedIndex = displays.findFocusedMonitor(
+ displays.eusToNwuQuat(breezyDesktop.imuRotations[0]),
+ breezyDesktop.monitorPlacements.map(monitorVectors => monitorVectors.centerLook),
+ breezyDesktop.focusedMonitorIndex,
+ false, // TODO smooth follow
+ breezyDesktop.fovDetails,
+ breezyDesktop.screens.map(screen => screen.geometry)
+ );
+
+ console.log(`\t\t\tBreezy - Next focused monitor index: ${focusedIndex}`);
+ if (focusedIndex !== breezyDesktop.focusedMonitorIndex)
+ breezyDesktop.focusedMonitorIndex = focusedIndex;
}
}
}
diff --git a/kwin/src/qml/CameraController.qml b/kwin/src/qml/CameraController.qml
index 84804ff..15b5276 100644
--- a/kwin/src/qml/CameraController.qml
+++ b/kwin/src/qml/CameraController.qml
@@ -15,7 +15,6 @@ Item {
property real lensDistanceRatio: effect.lensDistanceRatio
property bool sbsEnabled: effect.sbsEnabled
property bool customBannerEnabled: effect.customBannerEnabled
- property bool useImuRotation: true // Set to true to use XR rotation when available
implicitWidth: parent.width
implicitHeight: parent.height
@@ -72,7 +71,7 @@ Item {
FrameAnimation {
running: true
onTriggered: {
- if (root.useImuRotation && root.imuRotations && root.imuRotations.length > 0) {
+ if (root.imuRotations && root.imuRotations.length > 0) {
updateCamera(applyLookAhead(
root.imuRotations[0],
root.imuRotations[1],
diff --git a/kwin/src/qml/Displays.qml b/kwin/src/qml/Displays.qml
index 95ddde8..a126c62 100644
--- a/kwin/src/qml/Displays.qml
+++ b/kwin/src/qml/Displays.qml
@@ -1,7 +1,8 @@
import QtQuick
QtObject {
- property real displayDistance: effect.displayDistance
+ readonly property real focusThreshold: 0.95 / 2.0
+ readonly property real unfocusThreshold: 1.1 / 2.0
// Converts degrees to radians
function degreeToRadian(degree) {
@@ -12,6 +13,16 @@ QtObject {
return radian * 180 / Math.PI;
}
+ function nwuToEusVector(vector) {
+ // Converts NWU vector to EUS vector
+ return Qt.vector3d(-vector.y, vector.z, -vector.x);
+ }
+
+ function eusToNwuQuat(quaternion) {
+ // Converts EUS quaternion to NWU quaternion
+ return Qt.quaternion(quaternion.scalar, -quaternion.z, -quaternion.x, quaternion.y);
+ }
+
// Converts diagonal FOV in radians and aspect ratio to horizontal and vertical FOVs
function diagonalToCrossFOVs(diagonalFOVRadians, aspectRatio) {
var flatDiagonalFOV = 2 * Math.tan(diagonalFOVRadians / 2);
@@ -24,10 +35,6 @@ QtObject {
}
}
- function displayDistanceDefault() {
- return displayDistance;
- }
-
function actualWrapScheme(screens, viewportWidth, viewportHeight) {
const minX = Math.min(...screens.map(screen => screen.geometry.x));
const maxX = Math.max(...screens.map(screen => screen.geometry.x + screen.geometry.width));
@@ -44,8 +51,8 @@ QtObject {
function fovDetails(screens, viewportWidth, viewportHeight, viewportDiagonalFOV, lensDistanceRatio) {
const aspect = viewportWidth / viewportHeight;
const fovRadians = diagonalToCrossFOVs(degreeToRadian(viewportDiagonalFOV), aspect);
- const defaultDistanceVerticalRadians = 2 * Math.atan(Math.tan(fovRadians.vertical / 2) / displayDistanceDefault());
- const defaultDistanceHorizontalRadians = 2 * Math.atan(Math.tan(fovRadians.horizontal / 2) / displayDistanceDefault());
+ const defaultDistanceVerticalRadians = 2 * Math.atan(Math.tan(fovRadians.vertical / 2) / effect.allDisplaysDistance);
+ const defaultDistanceHorizontalRadians = 2 * Math.atan(Math.tan(fovRadians.horizontal / 2) / effect.allDisplaysDistance);
// distance needed for the FOV-sized monitor to fill up the screen
const fullScreenDistance = viewportHeight / 2 / Math.tan(fovRadians.vertical / 2);
@@ -106,26 +113,6 @@ QtObject {
}
})
- // Applies a quaternion to a 3D vector
- function applyQuaternionToVector(vector, quaternion) {
- var t = [
- 2.0 * (quaternion[1] * vector[2] - quaternion[2] * vector[1]),
- 2.0 * (quaternion[2] * vector[0] - quaternion[0] * vector[2]),
- 2.0 * (quaternion[0] * vector[1] - quaternion[1] * vector[0])
- ];
- return [
- vector[0] + quaternion[3] * t[0] + quaternion[1] * t[2] - quaternion[2] * t[1],
- vector[1] + quaternion[3] * t[1] + quaternion[2] * t[0] - quaternion[0] * t[2],
- vector[2] + quaternion[3] * t[2] + quaternion[0] * t[1] - quaternion[1] * t[0]
- ];
- }
-
- // Normalizes a 3D vector
- function normalizeVector(vector) {
- var length = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]);
- return [vector[0] / length, vector[1] / length, vector[2] / length];
- }
-
function monitorWrap(cachedMonitorRadians, monitorSpacingPixels, monitorBeginPixel, monitorLengthPixels, lengthToRadianFn) {
var closestWrapPixel = monitorBeginPixel;
var closestWrap = cachedMonitorRadians[monitorBeginPixel];
@@ -227,16 +214,16 @@ QtObject {
monitorPlacements.push({
originalIndex: originalIndex,
- centerNoRotate: [
+ centerNoRotate: Qt.vector3d(
monitorCenterRadius,
0,
upCenterPixels
- ],
- centerLook: normalizeVector([
+ ),
+ centerLook: Qt.vector3d(
monitorCenterRadius * Math.cos(monitorWrapDetails.center),
-monitorCenterRadius * Math.sin(monitorWrapDetails.center),
upCenterPixels
- ]),
+ ).normalized(),
rotationAngleRadians: {
x: 0,
y: -monitorWrapDetails.center
@@ -267,16 +254,16 @@ QtObject {
monitorPlacements.push({
originalIndex: originalIndex,
- centerNoRotate: [
+ centerNoRotate: Qt.vector3d(
monitorCenterRadius,
westCenterPixels,
0
- ],
- centerLook: normalizeVector([
+ ),
+ centerLook: Qt.vector3d(
monitorCenterRadius * Math.cos(monitorWrapDetails.center),
westCenterPixels,
-monitorCenterRadius * Math.sin(monitorWrapDetails.center)
- ]),
+ ).normalized(),
rotationAngleRadians: {
x: -monitorWrapDetails.center,
y: 0
@@ -295,16 +282,16 @@ QtObject {
monitorPlacements.push({
originalIndex: index,
- centerNoRotate: [
+ centerNoRotate: Qt.vector3d(
fovDetails.completeScreenDistancePixels,
westCenterPixels,
upCenterPixels
- ],
- centerLook: normalizeVector([
+ ),
+ centerLook: Qt.vector3d(
fovDetails.completeScreenDistancePixels,
westCenterPixels,
upCenterPixels
- ]),
+ ).normalized(),
rotationAngleRadians: {
x: 0,
y: 0
@@ -313,7 +300,108 @@ QtObject {
});
}
+ // put them back in the original monitor order before returning
monitorPlacements.sort(function(a, b) { return a.originalIndex - b.originalIndex; });
+
return monitorPlacements;
}
+
+ // returns how far the look vector is from the center of the monitor, as a percentage of the monitor's width
+ function getMonitorDistance(fovDetails, lookUpPixels, lookWestPixels, monitorVector, monitorDetails, upAngleToLength, westAngleToLength) {
+ var monitorAspectRatio = monitorDetails.width / monitorDetails.height;
+
+ // weight the up distance by the aspect ratio
+ var vectorUpPixels = upAngleToLength(
+ fovDetails.defaultDistanceVerticalRadians,
+ fovDetails.heightPixels,
+ fovDetails.completeScreenDistancePixels,
+ monitorVector.z,
+ monitorVector.x
+ );
+ var upDeltaPixels = (lookUpPixels - vectorUpPixels) * monitorAspectRatio;
+
+ var vectorWestPixels = westAngleToLength(
+ fovDetails.defaultDistanceHorizontalRadians,
+ fovDetails.widthPixels,
+ fovDetails.completeScreenDistancePixels,
+ monitorVector.y,
+ monitorVector.x
+ );
+ var westDeltaPixels = lookWestPixels - vectorWestPixels;
+ var totalDeltaPixels = Math.sqrt(upDeltaPixels * upDeltaPixels + westDeltaPixels * westDeltaPixels);
+
+ // threshold is a percentage of width, and height was already properly weighted
+ return totalDeltaPixels / monitorDetails.width;
+ }
+
+ function findFocusedMonitor(quaternion, monitorVectors, currentFocusedIndex, smoothFollowEnabled, fovDetails, monitorsDetails) {
+ var lookVector = Qt.vector3d(1.0, 0.0, 0.0); // NWU vector pointing to the center of the screen
+ var rotatedLookVector = quaternion.times(lookVector);
+
+ // Use curved or flat conversion functions depending on wrapping scheme
+ var upConversionFns = fovDetails.monitorWrappingScheme === "vertical" ? fovConversionFns.curved : fovConversionFns.flat;
+ var lookUpPixels = upConversionFns.angleToLength(
+ fovDetails.defaultDistanceVerticalRadians,
+ fovDetails.heightPixels,
+ fovDetails.completeScreenDistancePixels,
+ rotatedLookVector.z,
+ rotatedLookVector.x
+ );
+ var westConversionFns = fovDetails.monitorWrappingScheme === "horizontal" ? fovConversionFns.curved : fovConversionFns.flat;
+ var lookWestPixels = westConversionFns.angleToLength(
+ fovDetails.defaultDistanceHorizontalRadians,
+ fovDetails.widthPixels,
+ fovDetails.completeScreenDistancePixels,
+ rotatedLookVector.y,
+ rotatedLookVector.x
+ );
+
+ var closestIndex = -1;
+ var closestDistance = Number.POSITIVE_INFINITY;
+
+ // Check current focused monitor first
+ if (currentFocusedIndex !== -1) {
+ var focusedDistance = getMonitorDistance(
+ fovDetails,
+ lookUpPixels,
+ lookWestPixels,
+ monitorVectors[currentFocusedIndex],
+ monitorsDetails[currentFocusedIndex],
+ upConversionFns.angleToLength,
+ westConversionFns.angleToLength
+ ) * effect.focusedDisplayDistance / effect.allDisplaysDistance;
+
+ console.log(`\t\t\tBreezy - Focused monitor index: ${currentFocusedIndex}, distance: ${focusedDistance}`);
+
+ if (smoothFollowEnabled || focusedDistance < unfocusThreshold)
+ return currentFocusedIndex;
+ }
+
+ // Find the closest monitor
+ for (var i = 0; i < monitorVectors.length; ++i) {
+ if (i === currentFocusedIndex)
+ continue;
+ var distance = getMonitorDistance(
+ fovDetails,
+ lookUpPixels,
+ lookWestPixels,
+ monitorVectors[i],
+ monitorsDetails[i],
+ upConversionFns.angleToLength,
+ westConversionFns.angleToLength
+ );
+ console.log(`\t\t\tBreezy - Monitor index: ${i}, distance: ${distance}`);
+
+ if (distance < closestDistance) {
+ closestIndex = i;
+ closestDistance = distance;
+ }
+ }
+
+ if (smoothFollowEnabled || closestDistance < focusThreshold)
+ return closestIndex;
+
+ // Unfocus all displays
+ return -1;
+ }
}
\ No newline at end of file
diff --git a/kwin/src/qml/main.qml b/kwin/src/qml/main.qml
index 468af61..a40c553 100644
--- a/kwin/src/qml/main.qml
+++ b/kwin/src/qml/main.qml
@@ -33,8 +33,9 @@ Item {
id: displays
}
+ property var fovDetails: displays.fovDetails(screens, viewportResolution[0], viewportResolution[1], viewportDiagonalFOVDegrees, effect.lensDistanceRatio)
+
property var monitorPlacements: {
- const fovDetails = displays.fovDetails(screens, viewportResolution[0], viewportResolution[1], viewportDiagonalFOVDegrees, effect.lensDistanceRatio);
const monitorSpacing = 0.0;
return displays.monitorsToPlacements(fovDetails, screens.map(screen => screen.geometry), monitorSpacing);
}
@@ -58,7 +59,6 @@ Item {
PerspectiveCamera {
id: camera
- frustumCullingEnabled: false
}
BreezyDesktop {