This commit is contained in:
wheaney 2026-01-10 10:00:10 -08:00
parent f4fecdc3e5
commit efd652b77c
9 changed files with 191 additions and 74 deletions

View File

@ -29,6 +29,13 @@
<label>Display Spacing</label> <label>Display Spacing</label>
<description>How far apart the displays are visually (not logically)</description> <description>How far apart the displays are visually (not logically)</description>
</entry> </entry>
<entry name="DisplaySize" type="Int">
<default>100</default>
<min>10</min>
<max>250</max>
<label>Display Size</label>
<description>Scale the displays in XR (percentage)</description>
</entry>
<entry name="DisplayHorizontalOffset" type="Int"> <entry name="DisplayHorizontalOffset" type="Int">
<default>0</default> <default>0</default>
<min>-250</min> <min>-250</min>

View File

@ -243,6 +243,7 @@ void BreezyDesktopEffect::reconfigure(ReconfigureFlags)
setFocusedDisplayDistance(BreezyDesktopConfig::focusedDisplayDistance() / 100.0f); setFocusedDisplayDistance(BreezyDesktopConfig::focusedDisplayDistance() / 100.0f);
setAllDisplaysDistance(BreezyDesktopConfig::allDisplaysDistance() / 100.0f); setAllDisplaysDistance(BreezyDesktopConfig::allDisplaysDistance() / 100.0f);
setDisplaySpacing(BreezyDesktopConfig::displaySpacing() / 1000.0f); setDisplaySpacing(BreezyDesktopConfig::displaySpacing() / 1000.0f);
setDisplaySize(BreezyDesktopConfig::displaySize() / 100.0f);
setZoomOnFocusEnabled(BreezyDesktopConfig::zoomOnFocusEnabled()); setZoomOnFocusEnabled(BreezyDesktopConfig::zoomOnFocusEnabled());
setSmoothFollowThreshold(BreezyDesktopConfig::smoothFollowThreshold()); setSmoothFollowThreshold(BreezyDesktopConfig::smoothFollowThreshold());
@ -515,6 +516,18 @@ void BreezyDesktopEffect::setDisplaySpacing(qreal spacing) {
} }
} }
qreal BreezyDesktopEffect::displaySize() const {
return m_displaySize;
}
void BreezyDesktopEffect::setDisplaySize(qreal size) {
const qreal clamped = std::clamp(size, 0.5, 2.0);
if (!qFuzzyCompare(clamped, m_displaySize)) {
m_displaySize = clamped;
Q_EMIT displaySizeChanged();
}
}
qreal BreezyDesktopEffect::displayHorizontalOffset() const { qreal BreezyDesktopEffect::displayHorizontalOffset() const {
return m_displayHorizontalOffset; return m_displayHorizontalOffset;
} }

View File

@ -38,6 +38,7 @@ namespace KWin
Q_PROPERTY(qreal focusedDisplayDistance READ focusedDisplayDistance NOTIFY focusedDisplayDistanceChanged) Q_PROPERTY(qreal focusedDisplayDistance READ focusedDisplayDistance NOTIFY focusedDisplayDistanceChanged)
Q_PROPERTY(qreal allDisplaysDistance READ allDisplaysDistance NOTIFY allDisplaysDistanceChanged) Q_PROPERTY(qreal allDisplaysDistance READ allDisplaysDistance NOTIFY allDisplaysDistanceChanged)
Q_PROPERTY(qreal displaySpacing READ displaySpacing NOTIFY displaySpacingChanged) Q_PROPERTY(qreal displaySpacing READ displaySpacing NOTIFY displaySpacingChanged)
Q_PROPERTY(qreal displaySize READ displaySize NOTIFY displaySizeChanged)
Q_PROPERTY(qreal displayHorizontalOffset READ displayHorizontalOffset NOTIFY displayOffsetChanged) Q_PROPERTY(qreal displayHorizontalOffset READ displayHorizontalOffset NOTIFY displayOffsetChanged)
Q_PROPERTY(qreal displayVerticalOffset READ displayVerticalOffset NOTIFY displayOffsetChanged) Q_PROPERTY(qreal displayVerticalOffset READ displayVerticalOffset NOTIFY displayOffsetChanged)
Q_PROPERTY(int displayWrappingScheme READ displayWrappingScheme NOTIFY displayWrappingSchemeChanged) Q_PROPERTY(int displayWrappingScheme READ displayWrappingScheme NOTIFY displayWrappingSchemeChanged)
@ -89,6 +90,8 @@ namespace KWin
void setAllDisplaysDistance(qreal distance); void setAllDisplaysDistance(qreal distance);
qreal displaySpacing() const; qreal displaySpacing() const;
void setDisplaySpacing(qreal spacing); void setDisplaySpacing(qreal spacing);
qreal displaySize() const;
void setDisplaySize(qreal size);
qreal displayHorizontalOffset() const; qreal displayHorizontalOffset() const;
qreal displayVerticalOffset() const; qreal displayVerticalOffset() const;
int displayWrappingScheme() const; int displayWrappingScheme() const;
@ -127,6 +130,7 @@ namespace KWin
void focusedDisplayDistanceChanged(); void focusedDisplayDistanceChanged();
void allDisplaysDistanceChanged(); void allDisplaysDistanceChanged();
void displaySpacingChanged(); void displaySpacingChanged();
void displaySizeChanged();
void displayOffsetChanged(); void displayOffsetChanged();
void displayWrappingSchemeChanged(); void displayWrappingSchemeChanged();
void enabledStateChanged(); void enabledStateChanged();
@ -191,6 +195,7 @@ namespace KWin
qreal m_focusedDisplayDistance = 0.85; qreal m_focusedDisplayDistance = 0.85;
qreal m_allDisplaysDistance = 1.05; qreal m_allDisplaysDistance = 1.05;
qreal m_displaySpacing = 0.0; qreal m_displaySpacing = 0.0;
qreal m_displaySize = 1.0;
qreal m_displayHorizontalOffset = 0.0; qreal m_displayHorizontalOffset = 0.0;
qreal m_displayVerticalOffset = 0.0; qreal m_displayVerticalOffset = 0.0;
int m_displayWrappingScheme = 0; // 0=auto,1=horizontal,2=vertical,3=flat int m_displayWrappingScheme = 0; // 0=auto,1=horizontal,2=vertical,3=flat

View File

@ -330,6 +330,7 @@ BreezyDesktopEffectConfig::BreezyDesktopEffectConfig(QObject *parent, const KPlu
connect(ui.kcfg_ZoomOnFocusEnabled, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::save); connect(ui.kcfg_ZoomOnFocusEnabled, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::save);
connect(ui.kcfg_FocusedDisplayDistance, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save); connect(ui.kcfg_FocusedDisplayDistance, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save);
connect(ui.kcfg_AllDisplaysDistance, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save); connect(ui.kcfg_AllDisplaysDistance, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save);
connect(ui.kcfg_DisplaySize, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save);
connect(ui.kcfg_DisplaySpacing, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save); connect(ui.kcfg_DisplaySpacing, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save);
connect(ui.kcfg_SmoothFollowThreshold, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save); connect(ui.kcfg_SmoothFollowThreshold, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save);
connect(ui.kcfg_DisplayHorizontalOffset, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save); connect(ui.kcfg_DisplayHorizontalOffset, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save);
@ -456,6 +457,7 @@ void BreezyDesktopEffectConfig::updateUiFromConfig()
{ {
ui.kcfg_FocusedDisplayDistance->setValue(BreezyDesktopConfig::self()->focusedDisplayDistance()); ui.kcfg_FocusedDisplayDistance->setValue(BreezyDesktopConfig::self()->focusedDisplayDistance());
ui.kcfg_AllDisplaysDistance->setValue(BreezyDesktopConfig::self()->allDisplaysDistance()); ui.kcfg_AllDisplaysDistance->setValue(BreezyDesktopConfig::self()->allDisplaysDistance());
ui.kcfg_DisplaySize->setValue(BreezyDesktopConfig::self()->displaySize());
ui.kcfg_DisplaySpacing->setValue(BreezyDesktopConfig::self()->displaySpacing()); ui.kcfg_DisplaySpacing->setValue(BreezyDesktopConfig::self()->displaySpacing());
ui.kcfg_DisplayHorizontalOffset->setValue(BreezyDesktopConfig::self()->displayHorizontalOffset()); ui.kcfg_DisplayHorizontalOffset->setValue(BreezyDesktopConfig::self()->displayHorizontalOffset());
ui.kcfg_DisplayVerticalOffset->setValue(BreezyDesktopConfig::self()->displayVerticalOffset()); ui.kcfg_DisplayVerticalOffset->setValue(BreezyDesktopConfig::self()->displayVerticalOffset());
@ -492,7 +494,7 @@ void BreezyDesktopEffectConfig::checkEffectLoaded() {
QPalette pal = warn->palette(); QPalette pal = warn->palette();
pal.setColor(QPalette::WindowText, QColor(Qt::red)); pal.setColor(QPalette::WindowText, QColor(Qt::red));
warn->setPalette(pal); warn->setPalette(pal);
warn->setText(tr("The Breezy Desktop KWin effect is not loaded. Please log out and back in to enable it.")); warn->setText(tr("The Breezy Desktop KWin effect is disabled or not loaded. Please check the Desktop Effects dialog. Otherwise, log out and back in to enable it."));
warn->setVisible(true); warn->setVisible(true);
} }
} }

View File

@ -151,14 +151,23 @@
</widget> </widget>
</item> </item>
<item row="6" column="0"> <item row="6" column="0">
<widget class="QLabel" name="labelDisplaySpacing"> <widget class="QLabel" name="labelDisplaySize">
<property name="text"> <property name="text">
<string>Display Spacing:</string> <string>Display Size:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="1"> <item row="6" column="1">
<widget class="QSlider" name="kcfg_DisplaySpacing"> <widget class="LabeledSlider" name="kcfg_DisplaySize">
<property name="tickPosition">
<enum>QSlider::NoTicks</enum>
</property>
<property name="tickStartOffset">
<double>10</double>
</property>
<property name="tickInterval">
<double>25</double>
</property>
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
@ -168,13 +177,30 @@
</widget> </widget>
</item> </item>
<item row="7" column="0"> <item row="7" column="0">
<widget class="QLabel" name="labelDisplaySpacing">
<property name="text">
<string>Display Spacing:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QSlider" name="kcfg_DisplaySpacing">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tracking">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="labelFollowThreshold"> <widget class="QLabel" name="labelFollowThreshold">
<property name="text"> <property name="text">
<string>Follow threshold:</string> <string>Follow threshold:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="1"> <item row="8" column="1">
<widget class="LabeledSlider" name="kcfg_SmoothFollowThreshold"> <widget class="LabeledSlider" name="kcfg_SmoothFollowThreshold">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
@ -196,7 +222,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="8" column="0"> <item row="9" column="0">
<widget class="QLabel" name="labelVirtualDisplays"> <widget class="QLabel" name="labelVirtualDisplays">
<property name="text"> <property name="text">
<string>Add Virtual Display:</string> <string>Add Virtual Display:</string>
@ -209,7 +235,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="8" column="1"> <item row="9" column="1">
<widget class="QWidget" name="widgetVirtualDisplayButtons"> <widget class="QWidget" name="widgetVirtualDisplayButtons">
<property name="visible"> <property name="visible">
<bool>false</bool> <bool>false</bool>

View File

@ -17,7 +17,7 @@ Item {
property bool customBannerEnabled: effect.customBannerEnabled property bool customBannerEnabled: effect.customBannerEnabled
property bool smoothFollowEnabled: effect.smoothFollowEnabled property bool smoothFollowEnabled: effect.smoothFollowEnabled
property real lookAheadScanlineMs: effect.lookAheadConfig[2] property real lookAheadScanlineMs: effect.lookAheadConfig[2]
property var crossFovs: displays.diagonalToCrossFOVs( property var fovLengths: displays.diagonalToCrossFOVs(
displays.degreeToRadian(effect.diagonalFOV), displays.degreeToRadian(effect.diagonalFOV),
aspectRatio aspectRatio
); );
@ -84,7 +84,9 @@ Item {
} }
function buildPerspectiveMatrix() { function buildPerspectiveMatrix() {
const f = 1.0 / crossFovs.verticalTangent; const verticalTangent = fovLengths.heightUnitDistance / 2.0;
const horizontalTangent = fovLengths.widthUnitDistance / 2.0;
const f = 1.0 / verticalTangent;
const nf = 1.0 / (clipNear - clipFar); const nf = 1.0 / (clipNear - clipFar);
const m00 = f / aspectRatio; const m00 = f / aspectRatio;
const m11 = f; const m11 = f;
@ -102,13 +104,13 @@ Item {
function applyRollingShutterShear(rates) { function applyRollingShutterShear(rates) {
// Convert to maximum shift at bottom of frame // Convert to maximum shift at bottom of frame
const maxDxNdc = (rates.yaw * lookAheadScanlineMs) / crossFovs.horizontalTangent; const maxDxNdc = (rates.yaw * lookAheadScanlineMs) / horizontalTangent;
const maxDyNdc = -(rates.pitch * lookAheadScanlineMs) / crossFovs.verticalTangent; const maxDyNdc = -(rates.pitch * lookAheadScanlineMs) / verticalTangent;
let shx = maxDxNdc / 2.0; let shx = maxDxNdc / 2.0;
let shy = maxDyNdc / 2.0; let shy = maxDyNdc / 2.0;
const f = 1.0 / crossFovs.verticalTangent; const f = 1.0 / verticalTangent;
const nf = 1.0 / (clipNear - clipFar); const nf = 1.0 / (clipNear - clipFar);
const m00 = f / aspectRatio; const m00 = f / aspectRatio;
const m11 = f; const m11 = f;

View File

@ -26,27 +26,26 @@ ProceduralMesh {
const fov = mesh.fovDetails; const fov = mesh.fovDetails;
const monitor = mesh.monitorGeometry; const monitor = mesh.monitorGeometry;
const conv = fov.curvedDisplay ? mesh.fovConversionFns.curved
: mesh.fovConversionFns.flat;
const horizontalWrap = fov.monitorWrappingScheme === 'horizontal'; const horizontalWrap = fov.monitorWrappingScheme === 'horizontal';
const verticalWrap = fov.monitorWrappingScheme === 'vertical'; const horizontalConversions = horizontalWrap && fov.curvedDisplay ? fovConversionFns.curved : fovConversionFns.flat;
const sideEdgeDistance = conv.centerToFovEdgeDistance( const sideEdgeDistancePixels = horizontalConversions.centerToFovEdgeDistance(
fov.completeScreenDistancePixels, fov.widthPixels); fov.completeScreenDistancePixels, fov.sizeAdjustedWidthPixels);
const horizontalRadians = conv.lengthToRadians( const horizontalRadians = horizontalConversions.lengthToRadians(
fov.defaultDistanceHorizontalRadians, fov.defaultDistanceHorizontalRadians,
fov.widthPixels, fov.widthPixels,
sideEdgeDistance, sideEdgeDistancePixels,
monitor.width monitor.width
); );
const topEdgeDistance = conv.centerToFovEdgeDistance( const verticalWrap = fov.monitorWrappingScheme === 'vertical';
fov.completeScreenDistancePixels, fov.heightPixels); const verticalConversions = verticalWrap && fov.curvedDisplay ? fovConversionFns.curved : fovConversionFns.flat;
const verticalRadians = conv.lengthToRadians( const topEdgeDistancePixels = verticalConversions.centerToFovEdgeDistance(
fov.completeScreenDistancePixels, fov.sizeAdjustedHeightPixels);
const verticalRadians = verticalConversions.lengthToRadians(
fov.defaultDistanceVerticalRadians, fov.defaultDistanceVerticalRadians,
fov.heightPixels, fov.heightPixels,
topEdgeDistance, topEdgeDistancePixels,
monitor.height monitor.height
); );

View File

@ -23,17 +23,25 @@ QtObject {
return Qt.quaternion(quaternion.scalar, -quaternion.z, -quaternion.x, quaternion.y); return Qt.quaternion(quaternion.scalar, -quaternion.z, -quaternion.x, quaternion.y);
} }
// Converts diagonal FOV in radians and aspect ratio to horizontal and vertical FOVs // Converts diagonal FOV in radians and aspect ratio to horizontal and vertical FOV measurements
function diagonalToCrossFOVs(diagonalFOVRadians, aspectRatio) { function diagonalToCrossFOVs(diagonalFOVRadians, aspectRatio) {
var diagonalTangent = Math.tan(diagonalFOVRadians / 2); // first convert from a spherical FOV to a diagonal FOV on a flat plane at a unit distance of 1.0
var verticalTangent = diagonalTangent / Math.sqrt(1 + aspectRatio * aspectRatio); const diagonalLengthUnitDistance = 2 * Math.tan(diagonalFOVRadians / 2);
var horizontalTangent = verticalTangent * aspectRatio;
// then convert to flat plane horizontal and vertical FOVs
const heightUnitDistance = diagonalLengthUnitDistance / Math.sqrt(1 + aspectRatio * aspectRatio);
const widthUnitDistance = heightUnitDistance * aspectRatio;
return { return {
diagonal: diagonalFOVRadians, // then convert back to spherical FOV
horizontal: 2 * Math.atan(horizontalTangent), diagonalRadians: diagonalFOVRadians,
horizontalTangent: horizontalTangent, horizontalRadians: 2 * Math.atan(widthUnitDistance / 2),
vertical: 2 * Math.atan(verticalTangent), verticalRadians: 2 * Math.atan(heightUnitDistance / 2),
verticalTangent: verticalTangent
// flat values are relative to a unit distance of 1.0
diagonalLengthUnitDistance,
widthUnitDistance,
heightUnitDistance
} }
} }
@ -50,33 +58,55 @@ QtObject {
} }
} }
function buildFovDetails(screens, viewportWidth, viewportHeight, viewportDiagonalFOV, lensDistanceRatio, defaultDisplayDistance, wrappingChoice) { function buildFovDetails(screens, viewportWidth, viewportHeight, viewportDiagonalFOV, lensDistanceRatio, defaultDisplayDistance, wrappingChoice, distanceAdjustedSize) {
const aspect = viewportWidth / viewportHeight; const aspect = viewportWidth / viewportHeight;
const crossFovs = diagonalToCrossFOVs(degreeToRadian(viewportDiagonalFOV), aspect); const fovLengths = diagonalToCrossFOVs(degreeToRadian(viewportDiagonalFOV), aspect);
const defaultDistanceVerticalRadians = 2 * Math.atan(crossFovs.verticalTangent / defaultDisplayDistance);
const defaultDistanceHorizontalRadians = 2 * Math.atan(crossFovs.horizontalTangent / defaultDisplayDistance);
// distance needed for the FOV-sized monitor to fill up the screen
const fullScreenDistance = viewportHeight / (2 * crossFovs.verticalTangent);
const lensDistancePixels = fullScreenDistance / (1.0 - lensDistanceRatio) - fullScreenDistance;
// distance of a display at the default (most zoomed out) distance, plus the lens distance constant
const lensToScreenDistance = viewportHeight / (2 * Math.tan(defaultDistanceVerticalRadians / 2));
const completeScreenDistancePixels = lensToScreenDistance + lensDistancePixels;
let monitorWrappingScheme = actualWrapScheme(screens, viewportWidth, viewportHeight); let monitorWrappingScheme = actualWrapScheme(screens, viewportWidth, viewportHeight);
if (wrappingChoice === 1) monitorWrappingScheme = 'horizontal'; if (wrappingChoice === 1) monitorWrappingScheme = 'horizontal';
else if (wrappingChoice === 2) monitorWrappingScheme = 'vertical'; else if (wrappingChoice === 2) monitorWrappingScheme = 'vertical';
else if (wrappingChoice === 3) monitorWrappingScheme = 'flat'; else if (wrappingChoice === 3) monitorWrappingScheme = 'flat';
const lensDistanceComplement = 1.0 - lensDistanceRatio;
const lensDistanceFactor = (1.0 / lensDistanceComplement) - 1.0;
const horizontalConversions = effect.curvedDisplay && monitorWrappingScheme === 'horizontal' ? fovConversionFns.curved : fovConversionFns.flat;
const verticalConversions = effect.curvedDisplay && monitorWrappingScheme === 'vertical' ? fovConversionFns.curved : fovConversionFns.flat;
const defaultDistanceVerticalRadians = verticalConversions.fovRadiansAtDistance(
fovLengths.verticalRadians,
fovLengths.heightUnitDistance,
defaultDisplayDistance
);
const defaultDistanceHorizontalRadians = horizontalConversions.fovRadiansAtDistance(
fovLengths.horizontalRadians,
fovLengths.widthUnitDistance,
defaultDisplayDistance
);
// distance needed for the FOV-sized monitor to fill up the screen, as measured from the lenses
const lensToUnitDistancePixels = viewportWidth / fovLengths.widthUnitDistance;
// distance from pivot point to lens
const lensDistancePixels = lensToUnitDistancePixels * lensDistanceFactor;
// distance from pivot point to full screen (monitor at unit distance from lens)
const fullScreenDistancePixels = lensToUnitDistancePixels + lensDistancePixels;
// distance of a display at the default (most zoomed out) distance from the pivot point
const completeScreenDistancePixels = fullScreenDistancePixels * defaultDisplayDistance;
return { return {
widthPixels: viewportWidth, widthPixels: viewportWidth,
distanceAdjustedSize,
sizeAdjustedWidthPixels: viewportWidth * distanceAdjustedSize,
heightPixels: viewportHeight, heightPixels: viewportHeight,
sizeAdjustedHeightPixels: viewportHeight * distanceAdjustedSize,
defaultDistanceVerticalRadians, defaultDistanceVerticalRadians,
defaultDistanceHorizontalRadians, defaultDistanceHorizontalRadians,
lensDistancePixels, lensDistancePixels,
fullScreenDistancePixels,
completeScreenDistancePixels, completeScreenDistancePixels,
monitorWrappingScheme: monitorWrappingScheme, monitorWrappingScheme,
curvedDisplay: effect.curvedDisplay curvedDisplay: effect.curvedDisplay
}; };
} }
@ -99,6 +129,9 @@ QtObject {
angleToLength: function(fovRadians, fovLength, screenDistance, toAngleOpposite, toAngleAdjacent) { angleToLength: function(fovRadians, fovLength, screenDistance, toAngleOpposite, toAngleAdjacent) {
return toAngleOpposite / toAngleAdjacent * screenDistance; return toAngleOpposite / toAngleAdjacent * screenDistance;
}, },
fovRadiansAtDistance: function(fovRadians, unitLength, newScreenDistance) {
return 2 * Math.atan(unitLength / 2 / newScreenDistance);
},
radiansToSegments: function(screenRadians) { return 1; } radiansToSegments: function(screenRadians) { return 1; }
}, },
curved: { curved: {
@ -114,6 +147,9 @@ QtObject {
angleToLength: function(fovRadians, fovLength, screenDistance, toAngleOpposite, toAngleAdjacent) { angleToLength: function(fovRadians, fovLength, screenDistance, toAngleOpposite, toAngleAdjacent) {
return fovLength / fovRadians * Math.atan2(toAngleOpposite, toAngleAdjacent); return fovLength / fovRadians * Math.atan2(toAngleOpposite, toAngleAdjacent);
}, },
fovRadiansAtDistance: function(fovRadians, unitLength, newScreenDistance) {
return fovRadians / newScreenDistance;
},
radiansToSegments: function(screenRadians) { radiansToSegments: function(screenRadians) {
return Math.ceil(screenRadians * segmentsPerRadian); return Math.ceil(screenRadians * segmentsPerRadian);
} }
@ -198,8 +234,12 @@ QtObject {
var conversionFns = fovDetails.curvedDisplay ? fovConversionFns.curved : fovConversionFns.flat; var conversionFns = fovDetails.curvedDisplay ? fovConversionFns.curved : fovConversionFns.flat;
if (fovDetails.monitorWrappingScheme === 'horizontal') { if (fovDetails.monitorWrappingScheme === 'horizontal') {
var sideEdgeRadius = conversionFns.centerToFovEdgeDistance(fovDetails.completeScreenDistancePixels, fovDetails.widthPixels); // monitors wrap around us horizontally
var monitorSpacingPixels = monitorSpacing * fovDetails.widthPixels;
var sideEdgeRadius = conversionFns.centerToFovEdgeDistance(fovDetails.completeScreenDistancePixels, fovDetails.sizeAdjustedWidthPixels);
var monitorSpacingPixels = monitorSpacing * fovDetails.sizeAdjustedWidthPixels;
// targetWidth is assumed to aleady be size adjusted
var lengthToRadianFn = function(targetWidth) { var lengthToRadianFn = function(targetWidth) {
return conversionFns.lengthToRadians( return conversionFns.lengthToRadians(
fovDetails.defaultDistanceHorizontalRadians, fovDetails.defaultDistanceHorizontalRadians,
@ -209,14 +249,14 @@ QtObject {
); );
}; };
cachedMonitorRadians[0] = -fovDetails.defaultDistanceHorizontalRadians / 2; cachedMonitorRadians[0] = -lengthToRadianFn(fovDetails.sizeAdjustedWidthPixels) / 2;
horizontalMonitorSort(monitorDetailsList).forEach(function(obj) { horizontalMonitorSort(monitorDetailsList).forEach(function(obj) {
var monitorDetails = obj.monitorDetails; var monitorDetails = obj.monitorDetails;
var originalIndex = obj.originalIndex; var originalIndex = obj.originalIndex;
var monitorWrapDetails = monitorWrap(cachedMonitorRadians, monitorSpacingPixels, monitorDetails.x, monitorDetails.width, lengthToRadianFn); var monitorWrapDetails = monitorWrap(cachedMonitorRadians, monitorSpacingPixels, monitorDetails.x, monitorDetails.width, lengthToRadianFn);
var monitorCenterRadius = conversionFns.fovEdgeToScreenCenterDistance(sideEdgeRadius, monitorDetails.width); var monitorCenterRadius = conversionFns.fovEdgeToScreenCenterDistance(sideEdgeRadius, monitorDetails.width);
var upTopPixels = -monitorDetails.y - (monitorDetails.y / fovDetails.heightPixels) * monitorSpacingPixels; var upTopPixels = -monitorDetails.y - (monitorDetails.y / fovDetails.sizeAdjustedHeightPixels) * monitorSpacingPixels;
var upCenterOffsetPixels = (monitorDetails.height - fovDetails.heightPixels) / 2; var upCenterOffsetPixels = (monitorDetails.height - fovDetails.sizeAdjustedHeightPixels) / 2;
var upCenterPixels = upTopPixels - upCenterOffsetPixels; var upCenterPixels = upTopPixels - upCenterOffsetPixels;
monitorPlacements.push({ monitorPlacements.push({
@ -239,8 +279,8 @@ QtObject {
}); });
}); });
} else if (fovDetails.monitorWrappingScheme === 'vertical') { } else if (fovDetails.monitorWrappingScheme === 'vertical') {
var topEdgeRadius = conversionFns.centerToFovEdgeDistance(fovDetails.completeScreenDistancePixels, fovDetails.heightPixels); var topEdgeRadius = conversionFns.centerToFovEdgeDistance(fovDetails.completeScreenDistancePixels, fovDetails.sizeAdjustedHeightPixels);
var monitorSpacingPixels = monitorSpacing * fovDetails.heightPixels; var monitorSpacingPixels = monitorSpacing * fovDetails.sizeAdjustedHeightPixels;
var lengthToRadianFn = function(targetHeight) { var lengthToRadianFn = function(targetHeight) {
return conversionFns.lengthToRadians( return conversionFns.lengthToRadians(
fovDetails.defaultDistanceVerticalRadians, fovDetails.defaultDistanceVerticalRadians,
@ -250,14 +290,14 @@ QtObject {
); );
}; };
cachedMonitorRadians[0] = -fovDetails.defaultDistanceVerticalRadians / 2; cachedMonitorRadians[0] = -lengthToRadianFn(fovDetails.sizeAdjustedHeightPixels) / 2;
verticalMonitorSort(monitorDetailsList).forEach(function(obj) { verticalMonitorSort(monitorDetailsList).forEach(function(obj) {
var monitorDetails = obj.monitorDetails; var monitorDetails = obj.monitorDetails;
var originalIndex = obj.originalIndex; var originalIndex = obj.originalIndex;
var monitorWrapDetails = monitorWrap(cachedMonitorRadians, monitorSpacingPixels, monitorDetails.y, monitorDetails.height, lengthToRadianFn); var monitorWrapDetails = monitorWrap(cachedMonitorRadians, monitorSpacingPixels, monitorDetails.y, monitorDetails.height, lengthToRadianFn);
var monitorCenterRadius = conversionFns.fovEdgeToScreenCenterDistance(topEdgeRadius, monitorDetails.height); var monitorCenterRadius = conversionFns.fovEdgeToScreenCenterDistance(topEdgeRadius, monitorDetails.height);
var westLeftPixels = -monitorDetails.x - (monitorDetails.x / fovDetails.widthPixels) * monitorSpacingPixels; var westLeftPixels = -monitorDetails.x - (monitorDetails.x / fovDetails.sizeAdjustedWidthPixels) * monitorSpacingPixels;
var westCenterOffsetPixels = (monitorDetails.width - fovDetails.widthPixels) / 2; var westCenterOffsetPixels = (monitorDetails.width - fovDetails.sizeAdjustedWidthPixels) / 2;
var westCenterPixels = westLeftPixels - westCenterOffsetPixels; var westCenterPixels = westLeftPixels - westCenterOffsetPixels;
monitorPlacements.push({ monitorPlacements.push({
@ -280,12 +320,12 @@ QtObject {
}); });
}); });
} else { } else {
var monitorSpacingPixels = monitorSpacing * fovDetails.widthPixels; var monitorSpacingPixels = monitorSpacing * fovDetails.sizeAdjustedWidthPixels;
monitorDetailsList.forEach(function(monitorDetails, index) { monitorDetailsList.forEach(function(monitorDetails, index) {
var upTopPixels = -monitorDetails.y - (monitorDetails.y / fovDetails.heightPixels) * monitorSpacingPixels; var upTopPixels = -monitorDetails.y - (monitorDetails.y / fovDetails.sizeAdjustedHeightPixels) * monitorSpacingPixels;
var westLeftPixels = -monitorDetails.x - (monitorDetails.x / fovDetails.widthPixels) * monitorSpacingPixels; var westLeftPixels = -monitorDetails.x - (monitorDetails.x / fovDetails.sizeAdjustedWidthPixels) * monitorSpacingPixels;
var westCenterOffsetPixels = (monitorDetails.width - fovDetails.widthPixels) / 2; var westCenterOffsetPixels = (monitorDetails.width - fovDetails.sizeAdjustedWidthPixels) / 2;
var upCenterOffsetPixels = (monitorDetails.height - fovDetails.heightPixels) / 2; var upCenterOffsetPixels = (monitorDetails.height - fovDetails.sizeAdjustedHeightPixels) / 2;
var westCenterPixels = westLeftPixels - westCenterOffsetPixels; var westCenterPixels = westLeftPixels - westCenterOffsetPixels;
var upCenterPixels = upTopPixels - upCenterOffsetPixels; var upCenterPixels = upTopPixels - upCenterOffsetPixels;

View File

@ -33,19 +33,41 @@ Item {
property var screens: KWinComponents.Workspace.screens.filter(function(screen) { property var screens: KWinComponents.Workspace.screens.filter(function(screen) {
return mirrorPhysicalDisplays || screen.name.includes("BreezyDesktop") || supportedModels.some(model => screen.model.includes(model)); return mirrorPhysicalDisplays || screen.name.includes("BreezyDesktop") || supportedModels.some(model => screen.model.includes(model));
}) })
property var sizeAdjustedScreens: screens.map(function(screen) {
const sizeComplement = (1.0 - distanceAdjustedSize) / 2.0;
const sizeViewportOffsetX = sizeComplement * viewportResolution[0];
const sizeViewportOffsetY = sizeComplement * viewportResolution[1];
return {
geometry: {
x: screen.geometry.x * distanceAdjustedSize + sizeViewportOffsetX,
y: screen.geometry.y * distanceAdjustedSize + sizeViewportOffsetY,
width: screen.geometry.width * distanceAdjustedSize,
height: screen.geometry.height * distanceAdjustedSize
},
name: screen.name,
model: screen.model
};
})
property real distanceAdjustedSize: (effect.allDisplaysDistance - effect.lensDistanceRatio) *effect.displaySize
property var sizeAdjustedViewport: {
return {
width: viewportResolution[0] * distanceAdjustedSize,
height: viewportResolution[1] * distanceAdjustedSize
};
}
// x value for placing the viewport in the middle of all screens // x value for placing the viewport in the middle of all screens
property real screensXMid: { property real screensXMid: {
let xMin = Number.MAX_VALUE; let xMin = Number.MAX_VALUE;
let xMax = Number.MIN_VALUE; let xMax = Number.MIN_VALUE;
for (let i = 0; i < screens.length; i++) { for (let i = 0; i < sizeAdjustedScreens.length; i++) {
const geometry = screens[i].geometry; const geometry = sizeAdjustedScreens[i].geometry;
xMin = Math.min(xMin, geometry.x); xMin = Math.min(xMin, geometry.x);
xMax = Math.max(xMax, geometry.x + geometry.width); xMax = Math.max(xMax, geometry.x + geometry.width);
} }
return (xMin + xMax) / 2 - (viewportResolution[0] / 2); return (xMin + xMax) / 2 - (sizeAdjustedViewport.width / 2);
} }
// y value for placing the viewport in the middle of all screens // y value for placing the viewport in the middle of all screens
@ -53,13 +75,13 @@ Item {
let yMin = Number.MAX_VALUE; let yMin = Number.MAX_VALUE;
let yMax = Number.MIN_VALUE; let yMax = Number.MIN_VALUE;
for (let i = 0; i < screens.length; i++) { for (let i = 0; i < sizeAdjustedScreens.length; i++) {
const geometry = screens[i].geometry; const geometry = sizeAdjustedScreens[i].geometry;
yMin = Math.min(yMin, geometry.y); yMin = Math.min(yMin, geometry.y);
yMax = Math.max(yMax, geometry.y + geometry.height); yMax = Math.max(yMax, geometry.y + geometry.height);
} }
return (yMin + yMax) / 2 - (viewportResolution[1] / 2); return (yMin + yMax) / 2 - (sizeAdjustedViewport.height / 2);
} }
Displays { Displays {
@ -67,19 +89,20 @@ Item {
} }
property var fovDetails: displays.buildFovDetails( property var fovDetails: displays.buildFovDetails(
screens, sizeAdjustedScreens,
viewportResolution[0], sizeAdjustedViewport.width,
viewportResolution[1], sizeAdjustedViewport.height,
viewportDiagonalFOVDegrees, viewportDiagonalFOVDegrees,
effect.lensDistanceRatio, effect.lensDistanceRatio,
effect.allDisplaysDistance, effect.allDisplaysDistance,
effect.displayWrappingScheme effect.displayWrappingScheme,
distanceAdjustedSize
) )
property var monitorPlacements: { property var monitorPlacements: {
const dx = effect.displayHorizontalOffset * viewportResolution[0]; const dx = effect.displayHorizontalOffset * sizeAdjustedViewport.width;
const dy = effect.displayVerticalOffset * viewportResolution[1]; const dy = effect.displayVerticalOffset * sizeAdjustedViewport.height;
const adjustedGeometries = screens.map(screen => { const adjustedGeometries = sizeAdjustedScreens.map(screen => {
const g = screen.geometry; const g = screen.geometry;
return { return {
x: g.x - screensXMid + dx, x: g.x - screensXMid + dx,