From c2dfefd395160d77ab4447819a33beb10188f89f Mon Sep 17 00:00:00 2001 From: wheaney <42350981+wheaney@users.noreply.github.com> Date: Fri, 19 Sep 2025 15:14:25 -0700 Subject: [PATCH] Add curved display support --- kwin/src/breezydesktopconfig.kcfg | 5 ++ kwin/src/breezydesktopeffect.cpp | 7 ++ kwin/src/breezydesktopeffect.h | 4 + kwin/src/kcm/breezydesktopeffectkcm.cpp | 2 + kwin/src/kcm/breezydesktopeffectkcm.ui | 34 ++++++--- kwin/src/qml/BreezyDesktop.qml | 9 +-- kwin/src/qml/BreezyDesktopDisplay.qml | 98 ++++++++++++++++++++++++- kwin/src/qml/Displays.qml | 2 +- 8 files changed, 139 insertions(+), 22 deletions(-) diff --git a/kwin/src/breezydesktopconfig.kcfg b/kwin/src/breezydesktopconfig.kcfg index 0003e2d..99cfec4 100644 --- a/kwin/src/breezydesktopconfig.kcfg +++ b/kwin/src/breezydesktopconfig.kcfg @@ -86,5 +86,10 @@ Apply follow mode to all displays instead of only the focused display + + false + + Curve the displays around you + diff --git a/kwin/src/breezydesktopeffect.cpp b/kwin/src/breezydesktopeffect.cpp index dfd23d2..9ada276 100644 --- a/kwin/src/breezydesktopeffect.cpp +++ b/kwin/src/breezydesktopeffect.cpp @@ -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 BreezyDesktopEffect::smoothFollowOrigin() const { return m_smoothFollowOrigin; } diff --git a/kwin/src/breezydesktopeffect.h b/kwin/src/breezydesktopeffect.h index 049e453..ec9beda 100644 --- a/kwin/src/breezydesktopeffect.h +++ b/kwin/src/breezydesktopeffect.h @@ -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; diff --git a/kwin/src/kcm/breezydesktopeffectkcm.cpp b/kwin/src/kcm/breezydesktopeffectkcm.cpp index 6c44371..12ad2b8 100644 --- a/kwin/src/kcm/breezydesktopeffectkcm.cpp +++ b/kwin/src/kcm/breezydesktopeffectkcm.cpp @@ -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()); diff --git a/kwin/src/kcm/breezydesktopeffectkcm.ui b/kwin/src/kcm/breezydesktopeffectkcm.ui index 74a2b1d..61a1ea9 100644 --- a/kwin/src/kcm/breezydesktopeffectkcm.ui +++ b/kwin/src/kcm/breezydesktopeffectkcm.ui @@ -73,6 +73,16 @@ + + + Curved display + + + false + + + + Follow mode @@ -82,14 +92,14 @@ - + Focused Display Distance: - + 2 @@ -111,14 +121,14 @@ - + All Displays Distance: - + 2 @@ -140,14 +150,14 @@ - + Display Spacing: - + Qt::Horizontal @@ -157,14 +167,14 @@ - + Follow threshold: - + Qt::Horizontal @@ -186,7 +196,7 @@ - + Add Virtual Display: @@ -199,7 +209,7 @@ - + false @@ -232,7 +242,7 @@ - + false false @@ -245,7 +255,7 @@ - + diff --git a/kwin/src/qml/BreezyDesktop.qml b/kwin/src/qml/BreezyDesktop.qml index 6baf840..435d5fb 100644 --- a/kwin/src/qml/BreezyDesktop.qml +++ b/kwin/src/qml/BreezyDesktop.qml @@ -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: { diff --git a/kwin/src/qml/BreezyDesktopDisplay.qml b/kwin/src/qml/BreezyDesktopDisplay.qml index e192c11..0ab6763 100644 --- a/kwin/src/qml/BreezyDesktopDisplay.qml +++ b/kwin/src/qml/BreezyDesktopDisplay.qml @@ -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 diff --git a/kwin/src/qml/Displays.qml b/kwin/src/qml/Displays.qml index e82b98d..b30a495 100644 --- a/kwin/src/qml/Displays.qml +++ b/kwin/src/qml/Displays.qml @@ -75,7 +75,7 @@ QtObject { lensDistancePixels, completeScreenDistancePixels, monitorWrappingScheme: monitorWrappingScheme, - curvedDisplay: false // or true + curvedDisplay: effect.curvedDisplay }; }