Skip to content

Commit 6631dad

Browse files
2 parents ec20847 + 95cbc0c commit 6631dad

File tree

5 files changed

+161
-0
lines changed

5 files changed

+161
-0
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using DesktopClock.Utilities;
3+
4+
namespace DesktopClock.Tests;
5+
6+
public class PixelShifterTests
7+
{
8+
[Theory]
9+
[InlineData(5, 10)] // Evenly divisible.
10+
[InlineData(3, 10)] // Not evenly divisible.
11+
[InlineData(10, 5)] // Amount is larger than total.
12+
public void ShiftX_ShouldNotExceedMaxTotalShift(int shiftAmount, int maxTotalShift)
13+
{
14+
var shifter = new PixelShifter
15+
{
16+
PixelsPerShift = shiftAmount,
17+
MaxPixelOffset = maxTotalShift,
18+
};
19+
20+
double totalShiftX = 0;
21+
22+
// Test 100 times because it's random.
23+
for (var i = 0; i < 100; i++)
24+
{
25+
var shift = shifter.ShiftX();
26+
totalShiftX += shift;
27+
28+
Assert.InRange(Math.Abs(totalShiftX), 0, maxTotalShift);
29+
}
30+
}
31+
32+
[Theory]
33+
[InlineData(5, 10)] // Evenly divisible.
34+
[InlineData(3, 10)] // Not evenly divisible.
35+
[InlineData(10, 5)] // Amount is larger than total.
36+
public void ShiftY_ShouldNotExceedMaxTotalShift(int shiftAmount, int maxTotalShift)
37+
{
38+
var shifter = new PixelShifter
39+
{
40+
PixelsPerShift = shiftAmount,
41+
MaxPixelOffset = maxTotalShift,
42+
};
43+
44+
double totalShiftY = 0;
45+
46+
// Test 100 times because it's random.
47+
for (var i = 0; i < 100; i++)
48+
{
49+
var shift = shifter.ShiftY();
50+
totalShiftY += shift;
51+
52+
Assert.InRange(Math.Abs(totalShiftY), 0, maxTotalShift);
53+
}
54+
}
55+
}

DesktopClock/MainWindow.xaml.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using CommunityToolkit.Mvvm.ComponentModel;
1111
using CommunityToolkit.Mvvm.Input;
1212
using DesktopClock.Properties;
13+
using DesktopClock.Utilities;
1314
using H.NotifyIcon;
1415
using H.NotifyIcon.EfficiencyMode;
1516
using Humanizer;
@@ -27,6 +28,7 @@ public partial class MainWindow : Window
2728
private TaskbarIcon _trayIcon;
2829
private TimeZoneInfo _timeZone;
2930
private SoundPlayer _soundPlayer;
31+
private PixelShifter _pixelShifter;
3032

3133
/// <summary>
3234
/// The date and time to countdown to, or <c>null</c> if regular clock is desired.
@@ -40,6 +42,12 @@ public partial class MainWindow : Window
4042
[ObservableProperty]
4143
private string _currentTimeOrCountdownString;
4244

45+
/// <summary>
46+
/// The amount of margin applied in order to shift the clock's pixels and help prevent burn-in.
47+
/// </summary>
48+
[ObservableProperty]
49+
private Thickness _pixelShift;
50+
4351
public MainWindow()
4452
{
4553
InitializeComponent();
@@ -206,6 +214,8 @@ private void SystemClockTimer_SecondChanged(object sender, EventArgs e)
206214
{
207215
UpdateTimeString();
208216

217+
TryShiftPixels();
218+
209219
TryPlaySound();
210220
}
211221

@@ -262,6 +272,20 @@ private void TryPlaySound()
262272
}
263273
}
264274

275+
private void TryShiftPixels()
276+
{
277+
if (!Settings.Default.BurnInMitigation || DateTimeOffset.Now.Second != 0)
278+
return;
279+
280+
_pixelShifter ??= new();
281+
282+
Dispatcher.Invoke(() =>
283+
{
284+
Left += _pixelShifter.ShiftX();
285+
Top += _pixelShifter.ShiftY();
286+
});
287+
}
288+
265289
private void UpdateTimeString()
266290
{
267291
string GetTimeString()

DesktopClock/Properties/Settings.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,11 @@ private Settings()
179179
/// </remarks>
180180
public bool RightAligned { get; set; } = false;
181181

182+
/// <summary>
183+
/// Experimental: Shifts the clock periodically in order to reduce screen burn-in.
184+
/// </summary>
185+
public bool BurnInMitigation { get; set; } = false;
186+
182187
/// <summary>
183188
/// Path to a WAV file to be played on a specified interval.
184189
/// </summary>

DesktopClock/SettingsWindow.xaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,12 @@
195195
FontSize="10"
196196
Margin="0,0,0,12" />
197197

198+
<CheckBox Content="Burn-in Mitigation" IsChecked="{Binding Settings.BurnInMitigation, Mode=TwoWay}" />
199+
<TextBlock Text="Experimental: Shifts the clock periodically in order to reduce screen burn-in."
200+
FontStyle="Italic"
201+
FontSize="10"
202+
Margin="0,0,0,12" />
203+
198204
<TextBlock Text="WAV File Path:" />
199205
<Grid>
200206
<Grid.ColumnDefinitions>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System;
2+
3+
namespace DesktopClock.Utilities;
4+
5+
public class PixelShifter
6+
{
7+
private readonly Random _random = new();
8+
private double _totalShiftX;
9+
private double _totalShiftY;
10+
11+
/// <summary>
12+
/// The number of pixels that will be shifted each time.
13+
/// </summary>
14+
public int PixelsPerShift { get; set; } = 1;
15+
16+
/// <summary>
17+
/// The maximum amount of drift that can occur in each direction.
18+
/// </summary>
19+
public int MaxPixelOffset { get; set; } = 4;
20+
21+
/// <summary>
22+
/// Returns an amount to shift horizontally by while staying within the specified bounds.
23+
/// </summary>
24+
public double ShiftX()
25+
{
26+
double pixelsToMoveBy = GetRandomShift();
27+
pixelsToMoveBy = GetFinalShiftAmount(_totalShiftX, pixelsToMoveBy, MaxPixelOffset);
28+
_totalShiftX += pixelsToMoveBy;
29+
return pixelsToMoveBy;
30+
}
31+
32+
/// <summary>
33+
/// Returns an amount to shift vertically by while staying within the specified bounds.
34+
/// </summary>
35+
public double ShiftY()
36+
{
37+
double pixelsToMoveBy = GetRandomShift();
38+
pixelsToMoveBy = GetFinalShiftAmount(_totalShiftY, pixelsToMoveBy, MaxPixelOffset);
39+
_totalShiftY += pixelsToMoveBy;
40+
return pixelsToMoveBy;
41+
}
42+
43+
/// <summary>
44+
/// Returns a random amount to shift by within the specified amount.
45+
/// </summary>
46+
private int GetRandomShift() => _random.Next(-PixelsPerShift, PixelsPerShift + 1);
47+
48+
/// <summary>
49+
/// Returns a capped amount to shift by.
50+
/// </summary>
51+
/// <param name="current">The current total amount of shift that has occurred.</param>
52+
/// <param name="offset">The proposed amount to shift by this time.</param>
53+
/// <param name="max">The bounds to stay within in respect to the total shift.</param>
54+
private double GetFinalShiftAmount(double current, double offset, double max)
55+
{
56+
var newTotal = current + offset;
57+
58+
if (newTotal > max)
59+
{
60+
return max - current;
61+
}
62+
else if (newTotal < -max)
63+
{
64+
return -max - current;
65+
}
66+
else
67+
{
68+
return offset;
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)