فهرست منبع

Add burn-in mitigation (#53)

Daniel Chalmers 1 سال پیش
والد
کامیت
be3c01a6f7

+ 55 - 0
DesktopClock.Tests/PixelShifterTests.cs

@@ -0,0 +1,55 @@
+using System;
+using DesktopClock.Utilities;
+
+namespace DesktopClock.Tests;
+
+public class PixelShifterTests
+{
+    [Theory]
+    [InlineData(5, 10)] // Evenly divisible.
+    [InlineData(3, 10)] // Not evenly divisible.
+    [InlineData(10, 5)] // Amount is larger than total.
+    public void ShiftX_ShouldNotExceedMaxTotalShift(int shiftAmount, int maxTotalShift)
+    {
+        var shifter = new PixelShifter
+        {
+            PixelsPerShift = shiftAmount,
+            MaxPixelOffset = maxTotalShift,
+        };
+
+        double totalShiftX = 0;
+
+        // Test 100 times because it's random.
+        for (var i = 0; i < 100; i++)
+        {
+            var shift = shifter.ShiftX();
+            totalShiftX += shift;
+
+            Assert.InRange(Math.Abs(totalShiftX), 0, maxTotalShift);
+        }
+    }
+
+    [Theory]
+    [InlineData(5, 10)] // Evenly divisible.
+    [InlineData(3, 10)] // Not evenly divisible.
+    [InlineData(10, 5)] // Amount is larger than total.
+    public void ShiftY_ShouldNotExceedMaxTotalShift(int shiftAmount, int maxTotalShift)
+    {
+        var shifter = new PixelShifter
+        {
+            PixelsPerShift = shiftAmount,
+            MaxPixelOffset = maxTotalShift,
+        };
+
+        double totalShiftY = 0;
+
+        // Test 100 times because it's random.
+        for (var i = 0; i < 100; i++)
+        {
+            var shift = shifter.ShiftY();
+            totalShiftY += shift;
+
+            Assert.InRange(Math.Abs(totalShiftY), 0, maxTotalShift);
+        }
+    }
+}

+ 24 - 0
DesktopClock/MainWindow.xaml.cs

@@ -10,6 +10,7 @@ using System.Windows.Input;
 using CommunityToolkit.Mvvm.ComponentModel;
 using CommunityToolkit.Mvvm.Input;
 using DesktopClock.Properties;
+using DesktopClock.Utilities;
 using H.NotifyIcon;
 using H.NotifyIcon.EfficiencyMode;
 using Humanizer;
@@ -27,6 +28,7 @@ public partial class MainWindow : Window
     private TaskbarIcon _trayIcon;
     private TimeZoneInfo _timeZone;
     private SoundPlayer _soundPlayer;
+    private PixelShifter _pixelShifter;
 
     /// <summary>
     /// 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
     [ObservableProperty]
     private string _currentTimeOrCountdownString;
 
+    /// <summary>
+    /// The amount of margin applied in order to shift the clock's pixels and help prevent burn-in.
+    /// </summary>
+    [ObservableProperty]
+    private Thickness _pixelShift;
+
     public MainWindow()
     {
         InitializeComponent();
@@ -202,6 +210,8 @@ public partial class MainWindow : Window
     {
         UpdateTimeString();
 
+        TryShiftPixels();
+
         TryPlaySound();
     }
 
@@ -258,6 +268,20 @@ public partial class MainWindow : Window
         }
     }
 
+    private void TryShiftPixels()
+    {
+        if (!Settings.Default.BurnInMitigation || DateTimeOffset.Now.Second != 0)
+            return;
+
+        _pixelShifter ??= new();
+
+        Dispatcher.Invoke(() =>
+        {
+            Left += _pixelShifter.ShiftX();
+            Top += _pixelShifter.ShiftY();
+        });
+    }
+
     private void UpdateTimeString()
     {
         string GetTimeString()

+ 5 - 0
DesktopClock/Properties/Settings.cs

@@ -179,6 +179,11 @@ public sealed class Settings : INotifyPropertyChanged, IDisposable
     /// </remarks>
     public bool RightAligned { get; set; } = false;
 
+    /// <summary>
+    /// Experimental: Shifts the clock periodically in order to reduce screen burn-in.
+    /// </summary>
+    public bool BurnInMitigation { get; set; } = false;
+
     /// <summary>
     /// Path to a WAV file to be played on a specified interval.
     /// </summary>

+ 12 - 0
DesktopClock/SettingsWindow.xaml

@@ -195,6 +195,18 @@
                            FontSize="10"
                            Margin="0,0,0,12" />
 
+                <CheckBox Content="Right Aligned" IsChecked="{Binding Settings.RightAligned, Mode=TwoWay}" />
+                <TextBlock Text="Experimental: Keeps the clock window aligned to the right when the size changes."
+                           FontStyle="Italic"
+                           FontSize="10"
+                           Margin="0,0,0,12" />
+
+                <CheckBox Content="Burn-in Mitigation" IsChecked="{Binding Settings.BurnInMitigation, Mode=TwoWay}" />
+                <TextBlock Text="Experimental: Shifts the clock periodically in order to reduce screen burn-in."
+                           FontStyle="Italic"
+                           FontSize="10"
+                           Margin="0,0,0,12" />
+
                 <TextBlock Text="WAV File Path:" />
                 <Grid>
                     <Grid.ColumnDefinitions>

+ 71 - 0
DesktopClock/Utilities/PixelShifter.cs

@@ -0,0 +1,71 @@
+using System;
+
+namespace DesktopClock.Utilities;
+
+public class PixelShifter
+{
+    private readonly Random _random = new();
+    private double _totalShiftX;
+    private double _totalShiftY;
+
+    /// <summary>
+    /// The number of pixels that will be shifted each time.
+    /// </summary>
+    public int PixelsPerShift { get; set; } = 1;
+
+    /// <summary>
+    /// The maximum amount of drift that can occur in each direction.
+    /// </summary>
+    public int MaxPixelOffset { get; set; } = 4;
+
+    /// <summary>
+    /// Returns an amount to shift horizontally by while staying within the specified bounds.
+    /// </summary>
+    public double ShiftX()
+    {
+        double pixelsToMoveBy = GetRandomShift();
+        pixelsToMoveBy = GetFinalShiftAmount(_totalShiftX, pixelsToMoveBy, MaxPixelOffset);
+        _totalShiftX += pixelsToMoveBy;
+        return pixelsToMoveBy;
+    }
+
+    /// <summary>
+    /// Returns an amount to shift vertically by while staying within the specified bounds.
+    /// </summary>
+    public double ShiftY()
+    {
+        double pixelsToMoveBy = GetRandomShift();
+        pixelsToMoveBy = GetFinalShiftAmount(_totalShiftY, pixelsToMoveBy, MaxPixelOffset);
+        _totalShiftY += pixelsToMoveBy;
+        return pixelsToMoveBy;
+    }
+
+    /// <summary>
+    /// Returns a random amount to shift by within the specified amount.
+    /// </summary>
+    private int GetRandomShift() => _random.Next(-PixelsPerShift, PixelsPerShift + 1);
+
+    /// <summary>
+    /// Returns a capped amount to shift by.
+    /// </summary>
+    /// <param name="current">The current total amount of shift that has occurred.</param>
+    /// <param name="offset">The proposed amount to shift by this time.</param>
+    /// <param name="max">The bounds to stay within in respect to the total shift.</param>
+    private double GetFinalShiftAmount(double current, double offset, double max)
+    {
+        var newTotal = current + offset;
+
+        if (newTotal > max)
+        {
+            return max - current;
+        }
+        else if (newTotal < -max)
+        {
+            return -max - current;
+        }
+        else
+        {
+            return offset;
+        }
+    }
+}