Add curved display support

This commit is contained in:
wheaney 2025-09-19 15:14:25 -07:00
parent e395a0e2fc
commit c2dfefd395
8 changed files with 139 additions and 22 deletions

View File

@ -86,5 +86,10 @@
<label>All displays follow mode</label>
<description>Apply follow mode to all displays instead of only the focused display</description>
</entry>
<entry name="CurvedDisplay" type="Bool">
<default>false</default>
<label>Curved display</label>
<description>Curve the displays around you</description>
</entry>
</group>
</kcfg>

View File

@ -229,6 +229,9 @@ void BreezyDesktopEffect::reconfigure(ReconfigureFlags)
if (m_removeVirtualDisplaysOnDisable != removeVD) { m_removeVirtualDisplaysOnDisable = removeVD; Q_EMIT removeVirtualDisplaysOnDisableChanged(); }
if (m_mirrorPhysicalDisplays != mirrorPhysicalDisplays) { m_mirrorPhysicalDisplays = mirrorPhysicalDisplays; Q_EMIT mirrorPhysicalDisplaysChanged(); }
bool curved = BreezyDesktopConfig::curvedDisplay();
if (m_curvedDisplay != curved) { m_curvedDisplay = curved; Q_EMIT curvedDisplayChanged(); }
// this one doesn't have a signal, just always assign it
m_allDisplaysFollowMode = BreezyDesktopConfig::allDisplaysFollowMode();
}
@ -505,6 +508,10 @@ bool BreezyDesktopEffect::mirrorPhysicalDisplays() const {
return m_mirrorPhysicalDisplays;
}
bool BreezyDesktopEffect::curvedDisplay() const {
return m_curvedDisplay;
}
QList<QQuaternion> BreezyDesktopEffect::smoothFollowOrigin() const {
return m_smoothFollowOrigin;
}

View File

@ -44,6 +44,7 @@ namespace KWin
Q_PROPERTY(int antialiasingQuality READ antialiasingQuality NOTIFY antialiasingQualityChanged)
Q_PROPERTY(bool removeVirtualDisplaysOnDisable READ removeVirtualDisplaysOnDisable NOTIFY removeVirtualDisplaysOnDisableChanged)
Q_PROPERTY(bool mirrorPhysicalDisplays READ mirrorPhysicalDisplays NOTIFY mirrorPhysicalDisplaysChanged)
Q_PROPERTY(bool curvedDisplay READ curvedDisplay NOTIFY curvedDisplayChanged)
public:
@ -87,6 +88,7 @@ namespace KWin
int antialiasingQuality() const;
bool removeVirtualDisplaysOnDisable() const;
bool mirrorPhysicalDisplays() const;
bool curvedDisplay() const;
void showCursor();
void hideCursor();
@ -120,6 +122,7 @@ namespace KWin
void antialiasingQualityChanged();
void removeVirtualDisplaysOnDisableChanged();
void mirrorPhysicalDisplaysChanged();
void curvedDisplayChanged();
void cursorImageSourceChanged();
void cursorPosChanged();
@ -167,6 +170,7 @@ namespace KWin
int m_antialiasingQuality = 3; // 0=None, 1=Medium, 2=High, 3=VeryHigh
bool m_removeVirtualDisplaysOnDisable = true;
bool m_mirrorPhysicalDisplays = false;
bool m_curvedDisplay = false;
float m_smoothFollowThreshold = 1.0f;
bool m_allDisplaysFollowMode = false;
bool m_focusedSmoothFollowEnabled = false;

View File

@ -125,6 +125,7 @@ BreezyDesktopEffectConfig::BreezyDesktopEffectConfig(QObject *parent, const KPlu
connect(ui.kcfg_MirrorPhysicalDisplays, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::save);
connect(ui.kcfg_RemoveVirtualDisplaysOnDisable, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::save);
connect(ui.kcfg_AllDisplaysFollowMode, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::save);
connect(ui.kcfg_CurvedDisplay, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::save);
connect(ui.EnableMultitap, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::updateMultitapEnabled);
connect(ui.SmoothFollowTrackYaw, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::updateSmoothFollowTrackYaw);
connect(ui.SmoothFollowTrackPitch, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::updateSmoothFollowTrackPitch);
@ -257,6 +258,7 @@ void BreezyDesktopEffectConfig::updateUiFromConfig()
ui.kcfg_DisplayWrappingScheme->setCurrentIndex(BreezyDesktopConfig::self()->displayWrappingScheme());
ui.kcfg_AntialiasingQuality->setCurrentIndex(BreezyDesktopConfig::self()->antialiasingQuality());
ui.kcfg_MirrorPhysicalDisplays->setChecked(BreezyDesktopConfig::self()->mirrorPhysicalDisplays());
ui.kcfg_CurvedDisplay->setChecked(BreezyDesktopConfig::self()->curvedDisplay());
ui.kcfg_RemoveVirtualDisplaysOnDisable->setChecked(BreezyDesktopConfig::self()->removeVirtualDisplaysOnDisable());
ui.kcfg_AllDisplaysFollowMode->setChecked(BreezyDesktopConfig::self()->allDisplaysFollowMode());
ui.kcfg_ZoomOnFocusEnabled->setChecked(BreezyDesktopConfig::self()->zoomOnFocusEnabled());

View File

@ -73,6 +73,16 @@
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="kcfg_CurvedDisplay">
<property name="text">
<string>Curved display</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="SmoothFollowEnabled">
<property name="text">
<string>Follow mode</string>
@ -82,14 +92,14 @@
</property>
</widget>
</item>
<item row="3" column="0">
<item row="4" column="0">
<widget class="QLabel" name="labelFocusedDisplayDistance">
<property name="text">
<string>Focused Display Distance:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<item row="4" column="1">
<widget class="LabeledSlider" name="kcfg_FocusedDisplayDistance">
<property name="decimalShift">
<double>2</double>
@ -111,14 +121,14 @@
</property>
</widget>
</item>
<item row="4" column="0">
<item row="5" column="0">
<widget class="QLabel" name="labelAllDisplaysDistance">
<property name="text">
<string>All Displays Distance:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="5" column="1">
<widget class="LabeledSlider" name="kcfg_AllDisplaysDistance">
<property name="decimalShift">
<double>2</double>
@ -140,14 +150,14 @@
</property>
</widget>
</item>
<item row="5" column="0">
<item row="6" column="0">
<widget class="QLabel" name="labelDisplaySpacing">
<property name="text">
<string>Display Spacing:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<item row="6" column="1">
<widget class="QSlider" name="kcfg_DisplaySpacing">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -157,14 +167,14 @@
</property>
</widget>
</item>
<item row="6" column="0">
<item row="7" column="0">
<widget class="QLabel" name="labelFollowThreshold">
<property name="text">
<string>Follow threshold:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="7" column="1">
<widget class="LabeledSlider" name="kcfg_SmoothFollowThreshold">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -186,7 +196,7 @@
</property>
</widget>
</item>
<item row="7" column="0">
<item row="8" column="0">
<widget class="QLabel" name="labelVirtualDisplays">
<property name="text">
<string>Add Virtual Display:</string>
@ -199,7 +209,7 @@
</property>
</widget>
</item>
<item row="7" column="1">
<item row="8" column="1">
<widget class="QWidget" name="widgetVirtualDisplayButtons">
<property name="visible">
<bool>false</bool>
@ -232,7 +242,7 @@
</layout>
</widget>
</item>
<item row="8" column="0" colspan="2">
<item row="9" column="0" colspan="2">
<widget class="QWidget" name="widgetVirtualDisplayList">
<property name="visible"><bool>false</bool></property>
<property name="enabled"><bool>false</bool></property>
@ -245,7 +255,7 @@
</layout>
</widget>
</item>
<item row="9" column="0" colspan="2">
<item row="10" column="0" colspan="2">
<widget class="KShortcutsEditor" name="shortcutsEditor" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">

View File

@ -145,6 +145,7 @@ Node {
delegate: BreezyDesktopDisplay {
screen: breezyDesktop.screens[index]
monitorPlacement: breezyDesktop.monitorPlacements[index]
fovDetails: breezyDesktop.fovDetails
property real smoothFollowTransitionProgress: 0.0
property real monitorDistance: effect.allDisplaysDistance
@ -158,14 +159,6 @@ Node {
return matrix;
}
property vector3d screenScale: {
const geometry = screen.geometry;
// apparently the default model unit size is 100x100, so we scale it up to the screen size
return Qt.vector3d(geometry.width / 100, geometry.height / 100, 1);
}
scale: screenScale
eulerRotation.y: screenRotationY
eulerRotation.x: screenRotationX
position: {

View File

@ -1,5 +1,6 @@
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
Model {
id: display
@ -7,12 +8,107 @@ Model {
required property QtObject screen
required property var monitorPlacement
required property int index
required property var fovDetails
property string cursorImageSource: effect.cursorImageSource
property size cursorImageSize: effect.cursorImageSize
property point cursorPos: effect.cursorPos
source: "#Rectangle"
Displays {
id: displays
}
geometry: ProceduralMesh {
id: mesh
property var _meshArrays: generateMesh()
positions: _meshArrays.positions
uv0s: _meshArrays.uvs
indexes: _meshArrays.indices
primitiveMode: ProceduralMesh.TriangleStrip
function generateMesh() {
if (!display.fovDetails || !display.screen)
return { positions: [], uvs: [], indices: [] };
const fov = display.fovDetails;
const monitor = display.screen.geometry;
const conv = fov.curvedDisplay ? displays.fovConversionFns.curved
: displays.fovConversionFns.flat;
const horizontalWrap = fov.monitorWrappingScheme === 'horizontal';
const verticalWrap = fov.monitorWrappingScheme === 'vertical';
const sideEdgeDistance = conv.centerToFovEdgeDistance(
fov.completeScreenDistancePixels, fov.widthPixels);
const horizontalRadians = conv.lengthToRadians(
fov.defaultDistanceHorizontalRadians,
fov.widthPixels,
sideEdgeDistance,
monitor.width
);
const topEdgeDistance = conv.centerToFovEdgeDistance(
fov.completeScreenDistancePixels, fov.heightPixels);
const verticalRadians = conv.lengthToRadians(
fov.defaultDistanceVerticalRadians,
fov.heightPixels,
topEdgeDistance,
monitor.height
);
const positions = [];
const uvs = [];
const indices = [];
const radius = fov.completeScreenDistancePixels;
function vertexFor(s, t) {
let z = 0;
const xOffset = s - 0.5;
let x = xOffset * monitor.width;
if (fov.curvedDisplay && horizontalWrap) {
const xOffsetRadians = xOffset * horizontalRadians;
x = Math.sin(xOffsetRadians) * radius;
z = radius - Math.cos(xOffsetRadians) * radius;
}
const yOffset = t - 0.5;
let y = yOffset * monitor.height;
if (fov.curvedDisplay && verticalWrap) {
const yOffsetRadians = yOffset * verticalRadians;
y = Math.sin(yOffsetRadians) * radius;
z = radius - Math.cos(yOffsetRadians) * radius;
}
return { pos: Qt.vector3d(x, y, z), uv: Qt.vector2d(s, t) };
}
let segments = 1;
if (horizontalWrap) segments = conv.radiansToSegments(horizontalRadians);
if (verticalWrap) segments = conv.radiansToSegments(verticalRadians);
for (let i = 0; i <= segments; i++) {
const texFraction = i / segments;
// !verticalWrap also covers "flat" wrap scheme
const texX0 = !verticalWrap ? texFraction : 0;
const texX1 = !verticalWrap ? texFraction : 1;
const texY0 = verticalWrap ? texFraction : 1;
const texY1 = verticalWrap ? texFraction : 0;
let vtxB = vertexFor(texX0, texY0);
let vtxT = vertexFor(texX1, texY1);
positions.push(vtxB.pos);
positions.push(vtxT.pos);
uvs.push(vtxB.uv);
uvs.push(vtxT.uv);
}
return { positions: positions, uvs: uvs, indices: [] };
}
}
materials: [
CustomMaterial {
id: customMat

View File

@ -75,7 +75,7 @@ QtObject {
lensDistancePixels,
completeScreenDistancePixels,
monitorWrappingScheme: monitorWrappingScheme,
curvedDisplay: false // or true
curvedDisplay: effect.curvedDisplay
};
}