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> <label>All displays follow mode</label>
<description>Apply follow mode to all displays instead of only the focused display</description> <description>Apply follow mode to all displays instead of only the focused display</description>
</entry> </entry>
<entry name="CurvedDisplay" type="Bool">
<default>false</default>
<label>Curved display</label>
<description>Curve the displays around you</description>
</entry>
</group> </group>
</kcfg> </kcfg>

View File

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

View File

@ -44,6 +44,7 @@ namespace KWin
Q_PROPERTY(int antialiasingQuality READ antialiasingQuality NOTIFY antialiasingQualityChanged) Q_PROPERTY(int antialiasingQuality READ antialiasingQuality NOTIFY antialiasingQualityChanged)
Q_PROPERTY(bool removeVirtualDisplaysOnDisable READ removeVirtualDisplaysOnDisable NOTIFY removeVirtualDisplaysOnDisableChanged) Q_PROPERTY(bool removeVirtualDisplaysOnDisable READ removeVirtualDisplaysOnDisable NOTIFY removeVirtualDisplaysOnDisableChanged)
Q_PROPERTY(bool mirrorPhysicalDisplays READ mirrorPhysicalDisplays NOTIFY mirrorPhysicalDisplaysChanged) Q_PROPERTY(bool mirrorPhysicalDisplays READ mirrorPhysicalDisplays NOTIFY mirrorPhysicalDisplaysChanged)
Q_PROPERTY(bool curvedDisplay READ curvedDisplay NOTIFY curvedDisplayChanged)
public: public:
@ -87,6 +88,7 @@ namespace KWin
int antialiasingQuality() const; int antialiasingQuality() const;
bool removeVirtualDisplaysOnDisable() const; bool removeVirtualDisplaysOnDisable() const;
bool mirrorPhysicalDisplays() const; bool mirrorPhysicalDisplays() const;
bool curvedDisplay() const;
void showCursor(); void showCursor();
void hideCursor(); void hideCursor();
@ -120,6 +122,7 @@ namespace KWin
void antialiasingQualityChanged(); void antialiasingQualityChanged();
void removeVirtualDisplaysOnDisableChanged(); void removeVirtualDisplaysOnDisableChanged();
void mirrorPhysicalDisplaysChanged(); void mirrorPhysicalDisplaysChanged();
void curvedDisplayChanged();
void cursorImageSourceChanged(); void cursorImageSourceChanged();
void cursorPosChanged(); void cursorPosChanged();
@ -167,6 +170,7 @@ namespace KWin
int m_antialiasingQuality = 3; // 0=None, 1=Medium, 2=High, 3=VeryHigh int m_antialiasingQuality = 3; // 0=None, 1=Medium, 2=High, 3=VeryHigh
bool m_removeVirtualDisplaysOnDisable = true; bool m_removeVirtualDisplaysOnDisable = true;
bool m_mirrorPhysicalDisplays = false; bool m_mirrorPhysicalDisplays = false;
bool m_curvedDisplay = false;
float m_smoothFollowThreshold = 1.0f; float m_smoothFollowThreshold = 1.0f;
bool m_allDisplaysFollowMode = false; bool m_allDisplaysFollowMode = false;
bool m_focusedSmoothFollowEnabled = 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_MirrorPhysicalDisplays, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::save);
connect(ui.kcfg_RemoveVirtualDisplaysOnDisable, &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_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.EnableMultitap, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::updateMultitapEnabled);
connect(ui.SmoothFollowTrackYaw, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::updateSmoothFollowTrackYaw); connect(ui.SmoothFollowTrackYaw, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::updateSmoothFollowTrackYaw);
connect(ui.SmoothFollowTrackPitch, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::updateSmoothFollowTrackPitch); connect(ui.SmoothFollowTrackPitch, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::updateSmoothFollowTrackPitch);
@ -257,6 +258,7 @@ void BreezyDesktopEffectConfig::updateUiFromConfig()
ui.kcfg_DisplayWrappingScheme->setCurrentIndex(BreezyDesktopConfig::self()->displayWrappingScheme()); ui.kcfg_DisplayWrappingScheme->setCurrentIndex(BreezyDesktopConfig::self()->displayWrappingScheme());
ui.kcfg_AntialiasingQuality->setCurrentIndex(BreezyDesktopConfig::self()->antialiasingQuality()); ui.kcfg_AntialiasingQuality->setCurrentIndex(BreezyDesktopConfig::self()->antialiasingQuality());
ui.kcfg_MirrorPhysicalDisplays->setChecked(BreezyDesktopConfig::self()->mirrorPhysicalDisplays()); ui.kcfg_MirrorPhysicalDisplays->setChecked(BreezyDesktopConfig::self()->mirrorPhysicalDisplays());
ui.kcfg_CurvedDisplay->setChecked(BreezyDesktopConfig::self()->curvedDisplay());
ui.kcfg_RemoveVirtualDisplaysOnDisable->setChecked(BreezyDesktopConfig::self()->removeVirtualDisplaysOnDisable()); ui.kcfg_RemoveVirtualDisplaysOnDisable->setChecked(BreezyDesktopConfig::self()->removeVirtualDisplaysOnDisable());
ui.kcfg_AllDisplaysFollowMode->setChecked(BreezyDesktopConfig::self()->allDisplaysFollowMode()); ui.kcfg_AllDisplaysFollowMode->setChecked(BreezyDesktopConfig::self()->allDisplaysFollowMode());
ui.kcfg_ZoomOnFocusEnabled->setChecked(BreezyDesktopConfig::self()->zoomOnFocusEnabled()); ui.kcfg_ZoomOnFocusEnabled->setChecked(BreezyDesktopConfig::self()->zoomOnFocusEnabled());

View File

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

View File

@ -145,6 +145,7 @@ Node {
delegate: BreezyDesktopDisplay { delegate: BreezyDesktopDisplay {
screen: breezyDesktop.screens[index] screen: breezyDesktop.screens[index]
monitorPlacement: breezyDesktop.monitorPlacements[index] monitorPlacement: breezyDesktop.monitorPlacements[index]
fovDetails: breezyDesktop.fovDetails
property real smoothFollowTransitionProgress: 0.0 property real smoothFollowTransitionProgress: 0.0
property real monitorDistance: effect.allDisplaysDistance property real monitorDistance: effect.allDisplaysDistance
@ -158,14 +159,6 @@ Node {
return matrix; 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.y: screenRotationY
eulerRotation.x: screenRotationX eulerRotation.x: screenRotationX
position: { position: {

View File

@ -1,5 +1,6 @@
import QtQuick import QtQuick
import QtQuick3D import QtQuick3D
import QtQuick3D.Helpers
Model { Model {
id: display id: display
@ -7,12 +8,107 @@ Model {
required property QtObject screen required property QtObject screen
required property var monitorPlacement required property var monitorPlacement
required property int index required property int index
required property var fovDetails
property string cursorImageSource: effect.cursorImageSource property string cursorImageSource: effect.cursorImageSource
property size cursorImageSize: effect.cursorImageSize property size cursorImageSize: effect.cursorImageSize
property point cursorPos: effect.cursorPos 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: [ materials: [
CustomMaterial { CustomMaterial {
id: customMat id: customMat

View File

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