Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ qt6_add_shaders(projecteur projecteur_shaders
PREFIX "/shaders"
BASE "${CMAKE_CURRENT_SOURCE_DIR}/qml/shaders"
FILES qml/shaders/textzoom.frag
qml/shaders/diffusedot.frag
qml/shaders/dottrail.frag
)

ecm_add_qtwayland_client_protocol(projecteur
Expand Down Expand Up @@ -225,6 +227,8 @@ add_custom_target(non-sources SOURCES README.md CONTRIBUTING.md LICENSE.md
src/extra-devices.cc.in src/projecteurconfig.kcfg
src/org.projecteur.Projecteur.xml
qml/shaders/textzoom.frag
qml/shaders/diffusedot.frag
qml/shaders/dottrail.frag
55-projecteur.rules.in projecteur.notifyrc
cmake/templates/projecteur.desktop.in)

Expand Down
4 changes: 4 additions & 0 deletions cmake/templates/projecteur.1
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ dot.color=[Color] (HTML-color; #RRGGBB)
.TP
dot.opacity=[Double] (0 ... 1)
.TP
dot.mode=[String] (solid, diffuse)
.TP
dot.trail=[Bool] (false, true)
.TP
border=[Bool] (false, true)
.TP
border.size=[Integer] (0 ... 100)
Expand Down
14 changes: 13 additions & 1 deletion cmake/templates/projecteur.bash-completion
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ _projecteur()
commands="${commands} spot.size= spot.rotation= spot.shape= spot.shape.square.radius="
commands="${commands} spot.multi-screen= spot.overlay="
commands="${commands} spot.shape.star.points= spot.shape.star.innerradius= spot.shape.ngon.sides="
commands="${commands} shade= shade.opacity= shade.color= dot= dot.size= dot.color= dot.opacity="
commands="${commands} shade= shade.opacity= shade.color= dot= dot.size= dot.color= dot.opacity= dot.mode= dot.trail="
commands="${commands} border= border.size= border.color= border.opacity= zoom= zoom.factor="
commands="${commands} zoom.mode="

Expand Down Expand Up @@ -74,6 +74,18 @@ _projecteur()
COMPREPLY=( $(compgen -W "false true" -- $cur) )
fi
;;
"dot.mode")
if [ "${prev_prev}" = "=" ] || [ "${cur}" = "=" ]; then
[ "${cur}" = "=" ] && cur=""
COMPREPLY=( $(compgen -W "solid diffuse" -- $cur) )
fi
;;
"dot.trail")
if [ "${prev_prev}" = "=" ] || [ "${cur}" = "=" ]; then
[ "${cur}" = "=" ] && cur=""
COMPREPLY=( $(compgen -W "false true" -- $cur) )
fi
;;
"border")
if [ "${prev_prev}" = "=" ] || [ "${cur}" = "=" ]; then
[ "${cur}" = "=" ] && cur=""
Expand Down
2 changes: 1 addition & 1 deletion doc/USER-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Under **Preferences → Spotlight**, you can configure:

- spotlight size, shape, and rotation;
- shade color and opacity;
- center dot and border;
- center-dot appearance and fading trail, plus the spotlight border;
- cursor appearance;
- zoom level and content type;
- multi-screen behavior.
Expand Down
119 changes: 116 additions & 3 deletions qml/main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -235,18 +235,131 @@ Window {
enabled: false
}

Item {
id: dotTrailHistory
readonly property real currentX: centerRect.x + centerRect.width / 2
readonly property real currentY: centerRect.y + centerRect.height / 2
property real point1X: currentX
property real point1Y: currentY
property real point2X: currentX
property real point2Y: currentY
property real point3X: currentX
property real point3Y: currentY
property real point4X: currentX
property real point4Y: currentY
property real point5X: currentX
property real point5Y: currentY
property real point6X: currentX
property real point6Y: currentY

function reset() {
point1X = currentX; point1Y = currentY
point2X = currentX; point2Y = currentY
point3X = currentX; point3Y = currentY
point4X = currentX; point4Y = currentY
point5X = currentX; point5Y = currentY
point6X = currentX; point6Y = currentY
}

Timer {
interval: 24
repeat: true
running: mainWindow.visible && ProjecteurApp.overlayVisible
&& Settings.showCenterDot && Settings.dotTrailEnabled
onRunningChanged: if (running) dotTrailHistory.reset()
onTriggered: {
const dx = dotTrailHistory.currentX - dotTrailHistory.point1X
const dy = dotTrailHistory.currentY - dotTrailHistory.point1Y
if (dx * dx + dy * dy > 90000) {
dotTrailHistory.reset()
return
}
dotTrailHistory.point6X = dotTrailHistory.point5X
dotTrailHistory.point6Y = dotTrailHistory.point5Y
dotTrailHistory.point5X = dotTrailHistory.point4X
dotTrailHistory.point5Y = dotTrailHistory.point4Y
dotTrailHistory.point4X = dotTrailHistory.point3X
dotTrailHistory.point4Y = dotTrailHistory.point3Y
dotTrailHistory.point3X = dotTrailHistory.point2X
dotTrailHistory.point3Y = dotTrailHistory.point2Y
dotTrailHistory.point2X = dotTrailHistory.point1X
dotTrailHistory.point2Y = dotTrailHistory.point1Y
dotTrailHistory.point1X = dotTrailHistory.currentX
dotTrailHistory.point1Y = dotTrailHistory.currentY
}
}
}

ShaderEffect {
id: dotTrail
readonly property real margin: Math.max(4, Settings.dotSize)
readonly property real minimumX: Math.min(dotTrailHistory.currentX, dotTrailHistory.point1X,
dotTrailHistory.point2X, dotTrailHistory.point3X, dotTrailHistory.point4X,
dotTrailHistory.point5X, dotTrailHistory.point6X)
readonly property real maximumX: Math.max(dotTrailHistory.currentX, dotTrailHistory.point1X,
dotTrailHistory.point2X, dotTrailHistory.point3X, dotTrailHistory.point4X,
dotTrailHistory.point5X, dotTrailHistory.point6X)
readonly property real minimumY: Math.min(dotTrailHistory.currentY, dotTrailHistory.point1Y,
dotTrailHistory.point2Y, dotTrailHistory.point3Y, dotTrailHistory.point4Y,
dotTrailHistory.point5Y, dotTrailHistory.point6Y)
readonly property real maximumY: Math.max(dotTrailHistory.currentY, dotTrailHistory.point1Y,
dotTrailHistory.point2Y, dotTrailHistory.point3Y, dotTrailHistory.point4Y,
dotTrailHistory.point5Y, dotTrailHistory.point6Y)

x: minimumX - margin; y: minimumY - margin
width: Math.max(1, maximumX - minimumX + margin * 2)
height: Math.max(1, maximumY - minimumY + margin * 2)
z: 1
visible: Settings.showCenterDot && Settings.dotTrailEnabled
opacity: Settings.dotOpacity
property size outputSize: Qt.size(width, height)
property real dotSize: Settings.dotSize
property color dotColor: Settings.dotColor
property point point0: Qt.point(dotTrailHistory.currentX - x, dotTrailHistory.currentY - y)
property point point1: Qt.point(dotTrailHistory.point1X - x, dotTrailHistory.point1Y - y)
property point point2: Qt.point(dotTrailHistory.point2X - x, dotTrailHistory.point2Y - y)
property point point3: Qt.point(dotTrailHistory.point3X - x, dotTrailHistory.point3Y - y)
property point point4: Qt.point(dotTrailHistory.point4X - x, dotTrailHistory.point4Y - y)
property point point5: Qt.point(dotTrailHistory.point5X - x, dotTrailHistory.point5Y - y)
property point point6: Qt.point(dotTrailHistory.point6X - x, dotTrailHistory.point6Y - y)
fragmentShader: "qrc:/shaders/dottrail.frag.qsb"
}

Rectangle {
id: dotCursor
id: solidDotCursor
antialiasing: true
anchors.centerIn: centerRect
width: Settings.dotSize; height: width
radius: width*0.5
radius: width * 0.5
color: Settings.dotColor
visible: Settings.showCenterDot
z: 2
visible: Settings.showCenterDot && Settings.dotMode === "solid"
opacity: Settings.dotOpacity
enabled: false
}

ShaderEffect {
id: diffuseDotCursor
anchors.centerIn: centerRect
width: Math.max(24, Settings.dotSize * 5)
height: width
z: 2
visible: Settings.showCenterDot && Settings.dotMode === "diffuse"
opacity: Settings.dotOpacity
property size outputSize: Qt.size(width, height)
property real dotSize: Settings.dotSize
property color dotColor: Settings.dotColor
property real time: 0
fragmentShader: "qrc:/shaders/diffusedot.frag.qsb"

NumberAnimation on time {
from: 0; to: 100
duration: 100000
loops: Animation.Infinite
running: diffuseDotCursor.visible && ProjecteurApp.overlayVisible
}
}

Rectangle {
id: topRect
visible: spot.visible
Expand Down
41 changes: 41 additions & 0 deletions qml/shaders/diffusedot.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#version 440

layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;

layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
vec2 outputSize;
float dotSize;
float time;
vec4 dotColor;
};

float hash(vec2 position)
{
vec3 p = fract(vec3(position.xyx) * 0.1031);
p += dot(p, p.yzx + 33.33);
return fract((p.x + p.y) * p.z);
}

void main()
{
vec2 pixel = qt_TexCoord0 * outputSize;
vec2 center = outputSize * 0.5;
float radius = max(1.5, dotSize * 0.5);
float distanceFromCenter = length(pixel - center);

float frame = floor(time * 32.0);
float fineNoise = hash(floor(pixel * 0.8) + frame * vec2(7.0, 13.0));
float slowNoise = hash(floor(pixel * 0.32) + floor(time * 15.0));
float corePulse = 0.88 + 0.12 * sin(time * 37.0 + fineNoise * 6.28318);

float core = (1.0 - smoothstep(radius * 0.62, radius, distanceFromCenter)) * corePulse;
float haze = exp(-0.5 * pow(distanceFromCenter / (radius * 1.45), 2.0)) * 0.42;
float speckle = pow(fineNoise, 9.0) * slowNoise
* exp(-0.5 * pow(distanceFromCenter / (radius * 1.9), 2.0)) * 0.8;
float alpha = clamp(max(core, haze) + speckle, 0.0, 1.0) * dotColor.a;

fragColor = vec4(dotColor.rgb * alpha, alpha) * qt_Opacity;
}
51 changes: 51 additions & 0 deletions qml/shaders/dottrail.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#version 440

layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;

layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
vec2 outputSize;
float dotSize;
vec4 dotColor;
vec2 point0;
vec2 point1;
vec2 point2;
vec2 point3;
vec2 point4;
vec2 point5;
vec2 point6;
};

float segmentDistance(vec2 pixel, vec2 start, vec2 end)
{
vec2 segment = end - start;
float lengthSquared = dot(segment, segment);
float amount = lengthSquared > 0.001
? clamp(dot(pixel - start, segment) / lengthSquared, 0.0, 1.0)
: 0.0;
return length(pixel - (start + amount * segment));
}

float trailSegment(vec2 pixel, vec2 start, vec2 end, float opacity)
{
float radius = max(1.0, dotSize * 0.42);
float coverage = 1.0 - smoothstep(radius * 0.25, radius, segmentDistance(pixel, start, end));
return coverage * opacity;
}

void main()
{
vec2 pixel = qt_TexCoord0 * outputSize;
float alpha = 0.0;
alpha = max(alpha, trailSegment(pixel, point0, point1, 0.58));
alpha = max(alpha, trailSegment(pixel, point1, point2, 0.46));
alpha = max(alpha, trailSegment(pixel, point2, point3, 0.34));
alpha = max(alpha, trailSegment(pixel, point3, point4, 0.24));
alpha = max(alpha, trailSegment(pixel, point4, point5, 0.15));
alpha = max(alpha, trailSegment(pixel, point5, point6, 0.08));
alpha *= dotColor.a;

fragColor = vec4(dotColor.rgb * alpha, alpha) * qt_Opacity;
}
42 changes: 34 additions & 8 deletions src/preferencesdlg.cc
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ PreferencesDialog::PreferencesDialog(Settings* settings, Spotlight* spotlight,
connect(settings, &Settings::dotSizeChanged, this, modified);
connect(settings, &Settings::dotColorChanged, this, modified);
connect(settings, &Settings::dotOpacityChanged, this, modified);
connect(settings, &Settings::dotModeChanged, this, modified);
connect(settings, &Settings::dotTrailEnabledChanged, this, modified);
connect(settings, &Settings::shadeColorChanged, this, modified);
connect(settings, &Settings::shadeOpacityChanged, this, modified);
connect(settings, &Settings::cursorChanged, this, modified);
Expand Down Expand Up @@ -465,16 +467,40 @@ QGroupBox* PreferencesDialog::createDotGroupBox(Settings* settings)
connect(settings, &Settings::dotSizeChanged, this, &PreferencesDialog::resetPresetCombo);

const auto dotGrid = new QGridLayout(dotGroup);
dotGrid->addWidget(new QLabel(i18n("Dot Size"), this), 0, 0);
dotGrid->addLayout(dotsizeHBox, 0, 1);
const auto dotModeCombo = new QComboBox(this);
dotModeCombo->addItem(i18n("Solid"), QStringLiteral("solid"));
dotModeCombo->addItem(i18n("Diffuse scintillating"), QStringLiteral("diffuse"));
dotModeCombo->setCurrentIndex(dotModeCombo->findData(settings->dotMode()));
connect(dotModeCombo, &QComboBox::currentIndexChanged, settings,
[settings, dotModeCombo](int index) {
settings->setDotMode(dotModeCombo->itemData(index).toString());
});
connect(settings, &Settings::dotModeChanged, dotModeCombo,
[dotModeCombo](const QString& mode) {
const auto index = dotModeCombo->findData(mode);
if (index >= 0) { dotModeCombo->setCurrentIndex(index); }
});
connect(settings, &Settings::dotModeChanged, this, &PreferencesDialog::resetPresetCombo);
dotGrid->addWidget(new QLabel(i18n("Appearance"), this), 0, 0);
dotGrid->addWidget(dotModeCombo, 0, 1);

const auto dotTrailCheckBox = new QCheckBox(i18n("Show quickly fading trail"), this);
dotTrailCheckBox->setChecked(settings->dotTrailEnabled());
connect(dotTrailCheckBox, &QCheckBox::toggled, settings, &Settings::setDotTrailEnabled);
connect(settings, &Settings::dotTrailEnabledChanged, dotTrailCheckBox, &QCheckBox::setChecked);
connect(settings, &Settings::dotTrailEnabledChanged, this, &PreferencesDialog::resetPresetCombo);
dotGrid->addWidget(dotTrailCheckBox, 1, 0, 1, 2);

dotGrid->addWidget(new QLabel(i18n("Dot Size"), this), 2, 0);
dotGrid->addLayout(dotsizeHBox, 2, 1);

const auto dotColor = new KColorButton(settings->dotColor(), this);
dotColor->setAccessibleName(i18n("Dot Color"));
connect(dotColor, &KColorButton::changed, settings, &Settings::setDotColor);
connect(settings, &Settings::dotColorChanged, dotColor, &KColorButton::setColor);
connect(settings, &Settings::dotColorChanged, this, &PreferencesDialog::resetPresetCombo);
dotGrid->addWidget(new QLabel(i18n("Dot Color"), this), 1, 0);
dotGrid->addWidget(dotColor, 1, 1);
dotGrid->addWidget(new QLabel(i18n("Dot Color"), this), 3, 0);
dotGrid->addWidget(dotColor, 3, 1);


// Spotlight dot opacity setting
Expand All @@ -486,10 +512,10 @@ QGroupBox* PreferencesDialog::createDotGroupBox(Settings* settings)
dotOpacitySb->setValue(settings->dotOpacity());
connect(dotOpacitySb, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
settings, &Settings::setDotOpacity);
connect(settings, &Settings::borderOpacityChanged, dotOpacitySb, &QDoubleSpinBox::setValue);
connect(settings, &Settings::borderOpacityChanged, this, &PreferencesDialog::resetPresetCombo);
dotGrid->addWidget(new QLabel(i18n("Dot Opacity"), this), 2, 0);
dotGrid->addWidget(dotOpacitySb, 2, 1);
connect(settings, &Settings::dotOpacityChanged, dotOpacitySb, &QDoubleSpinBox::setValue);
connect(settings, &Settings::dotOpacityChanged, this, &PreferencesDialog::resetPresetCombo);
dotGrid->addWidget(new QLabel(i18n("Dot Opacity"), this), 4, 0);
dotGrid->addWidget(dotOpacitySb, 4, 1);

dotGrid->addWidget(new QWidget(this), 100, 0);
dotGrid->setRowStretch(100, 100);
Expand Down
6 changes: 6 additions & 0 deletions src/projecteurconfig.kcfg
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
<min>0.0</min>
<max>1.0</max>
</entry>
<entry name="DotMode" key="dotMode" type="String">
<default>solid</default>
</entry>
<entry name="DotTrailEnabled" key="dotTrailEnabled" type="Bool">
<default>false</default>
</entry>
<entry name="ShadeColor" key="shadeColor" type="Color">
<default>#222222</default>
</entry>
Expand Down
2 changes: 2 additions & 0 deletions src/projecteurcontrol.cc
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ ProjecteurControl::ProjecteurControl(ProjecteurApplication* application, Setting
connect(m_settings, &Settings::dotSizeChanged, this, settingsChanged);
connect(m_settings, &Settings::dotColorChanged, this, settingsChanged);
connect(m_settings, &Settings::dotOpacityChanged, this, settingsChanged);
connect(m_settings, &Settings::dotModeChanged, this, settingsChanged);
connect(m_settings, &Settings::dotTrailEnabledChanged, this, settingsChanged);
connect(m_settings, &Settings::shadeColorChanged, this, settingsChanged);
connect(m_settings, &Settings::shadeOpacityChanged, this, settingsChanged);
connect(m_settings, &Settings::cursorChanged, this, settingsChanged);
Expand Down
Loading