From 37a78862f36fc811581fbedff01a47245f257c2b Mon Sep 17 00:00:00 2001 From: Luna <34936608+Plootie@users.noreply.github.com> Date: Thu, 5 Jun 2025 00:29:28 +0100 Subject: [PATCH 1/6] Improve XAML readability + bug fixes Fixes font resource and makes the XAML much easier to read --- LockIn/LockIn.csproj | 7 ++-- LockIn/MainWindow.xaml | 73 ++++++++++++++++++++++++++++++++++-------- LockIn/Themes.xaml | 16 ++------- 3 files changed, 65 insertions(+), 31 deletions(-) diff --git a/LockIn/LockIn.csproj b/LockIn/LockIn.csproj index fa63612..5c0faa1 100644 --- a/LockIn/LockIn.csproj +++ b/LockIn/LockIn.csproj @@ -2,7 +2,7 @@ WinExe - net9.0-windows + net9.0-windows enable enable true @@ -22,9 +22,8 @@ - - PreserveNewest - + + diff --git a/LockIn/MainWindow.xaml b/LockIn/MainWindow.xaml index 80942ab..21c831d 100644 --- a/LockIn/MainWindow.xaml +++ b/LockIn/MainWindow.xaml @@ -3,11 +3,11 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:local="clr-namespace:LockIn" xmlns:vm="clr-namespace:LockIn.ViewModels" mc:Ignorable="d" d:DataContext="{d:DesignInstance Type=vm:MainWindowViewModel, IsDesignTimeCreatable=True}" - Title="Lock In" Height="300" Width="300"> + Title="Lock In" Height="275" Width="300"> + @@ -17,24 +17,69 @@ + Margin="10,5,0,0"> + + + + + + + + + - + + + + + + + + + + + + + - - + + + + + + + + + + + + + + - + VerticalAlignment="Top"> + + + + + + + + + + + + + diff --git a/LockIn/Themes.xaml b/LockIn/Themes.xaml index ffdb7db..f18c5d6 100644 --- a/LockIn/Themes.xaml +++ b/LockIn/Themes.xaml @@ -1,18 +1,8 @@  - - - - - + - - - + + /Assets/#FS Sinclair Regular.otf \ No newline at end of file From 57227dc666e71edb1fa15f1f5d4e93ee30f18d07 Mon Sep 17 00:00:00 2001 From: Luna <34936608+Plootie@users.noreply.github.com> Date: Thu, 5 Jun 2025 00:31:12 +0100 Subject: [PATCH 2/6] Move hardcoded brush to static resource --- LockIn/MainWindow.xaml | 2 +- LockIn/Themes.xaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/LockIn/MainWindow.xaml b/LockIn/MainWindow.xaml index 21c831d..49d85a3 100644 --- a/LockIn/MainWindow.xaml +++ b/LockIn/MainWindow.xaml @@ -8,7 +8,7 @@ d:DataContext="{d:DesignInstance Type=vm:MainWindowViewModel, IsDesignTimeCreatable=True}" Title="Lock In" Height="275" Width="300"> - + diff --git a/LockIn/Themes.xaml b/LockIn/Themes.xaml index f18c5d6..57dd5da 100644 --- a/LockIn/Themes.xaml +++ b/LockIn/Themes.xaml @@ -3,6 +3,7 @@ + /Assets/#FS Sinclair Regular.otf \ No newline at end of file From 42734bb17f8a928ac53f6e10be1926a2b6920ea6 Mon Sep 17 00:00:00 2001 From: Luna <34936608+Plootie@users.noreply.github.com> Date: Thu, 5 Jun 2025 01:06:03 +0100 Subject: [PATCH 3/6] Include all files in Assets as Resource --- LockIn/LockIn.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LockIn/LockIn.csproj b/LockIn/LockIn.csproj index 5c0faa1..155e445 100644 --- a/LockIn/LockIn.csproj +++ b/LockIn/LockIn.csproj @@ -22,8 +22,8 @@ - - + + From 8a8ae2eb078e1231213c411de9f0458582190976 Mon Sep 17 00:00:00 2001 From: Luna <34936608+Plootie@users.noreply.github.com> Date: Thu, 5 Jun 2025 01:06:37 +0100 Subject: [PATCH 4/6] Add Icon to application --- LockIn/Assets/Helldivers2.ico | Bin 0 -> 4158 bytes LockIn/MainWindow.xaml | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 LockIn/Assets/Helldivers2.ico diff --git a/LockIn/Assets/Helldivers2.ico b/LockIn/Assets/Helldivers2.ico new file mode 100644 index 0000000000000000000000000000000000000000..094da3839ca34fe5d213f9cd831d306b9fe15a0c GIT binary patch literal 4158 zcmeHKYiv|i5dIco65CE zVoWsQ55#Dq#1{%kp(Icqf(VU(w$%!>g(5*g-I;UNnR|2gUN3jo-Lyo0xXsyf&$+Yn z&3tEO&J;+5JvUqYX;nNL_Jv|29DT|&|3SHKsrOV)>HH_LBgfj|k zpW85gBBTAd@$kA8Gc;i+E?ttK$5*Z%n@Q;SBhaNfRF~7I z`t^C4oWbZZgq54wTf2QHud}n|hoc5l?#~b=j2+J##0cIoR-a2JXf0vDYxx?(jL(-^`S}iq_bt z=O@z~tBu}}`ZBdPd6I118-8x6Z6B{=3?CWT7S*fkvDY%z?PM>Xw2nn(o?Q@+{m|o4 zY>WE0d>%h(Vq7vZ;4i8(yuR)R$*DsIUvF1igWp^_n$akBIUb$go?nfO^DlC;7FUg9 zY-=;v+R7Mpx!eEuySlWD(fp&&y<)wvB#F6og!Q|E_;I-D+4}J@dr?wyp3uC{=Wb;) zp|B`B*I~bhjbQ9N;MWrMQT@^p^*4KmQ`( zSK%v@FS+h3=RM-XBhzxnzI&?NL((&J{AtspIt_Gdb`9y9o2lh$66JDRqsGX + Title="Lock In" Height="275" Width="300" + Icon="./Assets/Helldivers2.ico"> From f2e2cd78bb920d704fce632e6d5efbaa9235d400 Mon Sep 17 00:00:00 2001 From: Luna <34936608+Plootie@users.noreply.github.com> Date: Thu, 5 Jun 2025 02:06:00 +0100 Subject: [PATCH 5/6] Implement crude typewriter effect --- LockIn/ViewModels/MainWindowViewModel.cs | 66 ++++++++++++++++++------ 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/LockIn/ViewModels/MainWindowViewModel.cs b/LockIn/ViewModels/MainWindowViewModel.cs index 0ebb5d6..e59a558 100644 --- a/LockIn/ViewModels/MainWindowViewModel.cs +++ b/LockIn/ViewModels/MainWindowViewModel.cs @@ -1,5 +1,6 @@ using System.ComponentModel; using System.Diagnostics; +using System.Text; using System.Windows; using System.Windows.Threading; using CommunityToolkit.Mvvm.ComponentModel; @@ -22,6 +23,7 @@ public partial class MainWindowViewModel : ObservableObject private readonly Thread _gameFinder; private readonly Thread _cursorClipper; private readonly CancellationTokenSource _applicationExitTokenSource = new(); + private CancellationTokenSource _statusLabelAnimatorTokenSource = new(); public MainWindowViewModel() { @@ -46,21 +48,55 @@ private void QuitApplication(object sender, ExitEventArgs e) private void UpdateLabel() { + string textToSet = string.Empty; if (!IsLockEnabled) { - StatusText = "Disabled"; + textToSet = "Disabled"; _gameProcess = null; - return; } + else + { + if (_gameProcess == null || _gameProcess.HasExited) + { + textToSet = "Looking for game..."; + _threadSignaller.Set(); + } + else + { + textToSet = "Enabled"; + } + } + + _statusLabelAnimatorTokenSource.Cancel(); + _statusLabelAnimatorTokenSource = new CancellationTokenSource(); + _ = TypeWriterEffect(value => StatusText = value, textToSet, TimeSpan.FromMilliseconds(75), _statusLabelAnimatorTokenSource.Token); + } - if (_gameProcess == null || _gameProcess.HasExited) + private Task TypeWriterEffect(Action callback, string newText, TimeSpan timePerChar, CancellationToken ct = default) + { + try { - StatusText = "Looking for game..."; - _threadSignaller.Set(); + return Task.Run(() => + { + ct.ThrowIfCancellationRequested(); + callback(string.Empty); + StringBuilder bob = new(newText.Length); + foreach (char c in newText) + { + ct.ThrowIfCancellationRequested(); + bob.Append(c); + callback(bob.ToString()); + Thread.Sleep(timePerChar); + } + }, ct); } - else + catch (TaskCanceledException) + { + return Task.FromCanceled(ct); + } + catch (OperationCanceledException) { - StatusText = "Enabled"; + return Task.FromCanceled(ct); } } @@ -71,7 +107,11 @@ private void ThreadFindGame() while (_threadSignaller.WaitOne()) { if (_applicationExitTokenSource.Token.IsCancellationRequested) return; - Process[] processes = Process.GetProcessesByName("helldivers2"); + string processToFind = "helldivers2"; +#if DEBUG + processToFind = "notepad"; +#endif + Process[] processes = Process.GetProcessesByName(processToFind); if (processes.Length > 0) { _gameProcess = processes.First(); @@ -121,14 +161,8 @@ private void GameExit(object? sender, EventArgs e) partial void OnIsLockEnabledChanged(bool value) { - LockInText = value ? "Unlock" : "Lock In"; - } - - protected override void OnPropertyChanged(PropertyChangedEventArgs e) - { - base.OnPropertyChanged(e); - - if(e.PropertyName != nameof(IsLockEnabled)) return; + _ = TypeWriterEffect(strValue => LockInText = strValue, value ? "Unlock" : "Lock In", + TimeSpan.FromMilliseconds(80)); Natives.ClipCursor(IntPtr.Zero); UpdateLabel(); } From d35eead4f907ee9e8298b0deafd6e854cec0a43b Mon Sep 17 00:00:00 2001 From: Luna <34936608+Plootie@users.noreply.github.com> Date: Thu, 5 Jun 2025 04:15:47 +0100 Subject: [PATCH 6/6] Implement TypewriterTextBlock Implement typewriter logic into a user control for reduced code duplication --- LockIn/MainWindow.xaml | 40 ++++++----- LockIn/UserControls/TypewriterTextBlock.xaml | 8 +++ .../UserControls/TypewriterTextBlock.xaml.cs | 69 +++++++++++++++++++ LockIn/ViewModels/MainWindowViewModel.cs | 36 +--------- 4 files changed, 101 insertions(+), 52 deletions(-) create mode 100644 LockIn/UserControls/TypewriterTextBlock.xaml create mode 100644 LockIn/UserControls/TypewriterTextBlock.xaml.cs diff --git a/LockIn/MainWindow.xaml b/LockIn/MainWindow.xaml index d34374a..d1d1a42 100644 --- a/LockIn/MainWindow.xaml +++ b/LockIn/MainWindow.xaml @@ -4,9 +4,11 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="clr-namespace:LockIn.ViewModels" + xmlns:controls="clr-namespace:LockIn.UserControls" mc:Ignorable="d" d:DataContext="{d:DesignInstance Type=vm:MainWindowViewModel, IsDesignTimeCreatable=True}" Title="Lock In" Height="275" Width="300" + WindowStartupLocation="CenterScreen" Icon="./Assets/Helldivers2.ico"> @@ -31,20 +33,21 @@ - - + + - + - + - + - + - - + + @@ -63,24 +66,25 @@ - + VerticalAlignment="Top" + controls:TypewriterTextBlock.TextToAnimate="{Binding LockInText}"> - + - + - + - + - + - - + + diff --git a/LockIn/UserControls/TypewriterTextBlock.xaml b/LockIn/UserControls/TypewriterTextBlock.xaml new file mode 100644 index 0000000..31b0e7c --- /dev/null +++ b/LockIn/UserControls/TypewriterTextBlock.xaml @@ -0,0 +1,8 @@ + + diff --git a/LockIn/UserControls/TypewriterTextBlock.xaml.cs b/LockIn/UserControls/TypewriterTextBlock.xaml.cs new file mode 100644 index 0000000..c1774e9 --- /dev/null +++ b/LockIn/UserControls/TypewriterTextBlock.xaml.cs @@ -0,0 +1,69 @@ +using System.Text; +using System.Windows; +using System.Windows.Controls; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace LockIn.UserControls; + +[ObservableObject] +public partial class TypewriterTextBlock : TextBlock +{ + private CancellationTokenSource _cts = new CancellationTokenSource(); + public static readonly DependencyProperty TimePerCharacterProperty = DependencyProperty.RegisterAttached("TimePerCharacter", typeof(TimeSpan), typeof(TypewriterTextBlock), new PropertyMetadata(TimeSpan.FromMilliseconds(100))); + public static readonly DependencyProperty TextToAnimateProperty = DependencyProperty.RegisterAttached("TextToAnimate", typeof(string), typeof(TypewriterTextBlock), new PropertyMetadata(string.Empty, OnTextChanged)); + + public static void SetTimePerCharacter(DependencyObject element, TimeSpan value) + { + element.SetValue(TimePerCharacterProperty, value); + } + + public static TimeSpan GetTimePerCharacter(DependencyObject element) + { + return (TimeSpan)element.GetValue(TimePerCharacterProperty); + } + + public static void SetTextToAnimate(DependencyObject element, string value) + { + element.SetValue(TextToAnimateProperty, value); + } + + public static string GetTextToAnimate(DependencyObject element, string value) + { + return (string)element.GetValue(TextToAnimateProperty); + } + + public TypewriterTextBlock() + { + + } + + private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if(d is not TypewriterTextBlock textBlock) return; + if (e.NewValue == e.OldValue) return; + textBlock._cts.Cancel(); + textBlock._cts.Dispose(); + textBlock._cts = new CancellationTokenSource(); + _ = textBlock.AnimateText(e.NewValue.ToString() ?? string.Empty, textBlock._cts.Token); + } + + private async Task AnimateText(string textToAnimate, CancellationToken ct = default) + { + if(ct.IsCancellationRequested) return; + TimeSpan timePerCharacter = (TimeSpan)GetValue(TimePerCharacterProperty); + StringBuilder bob = new(textToAnimate.Length); + try + { + foreach (char c in textToAnimate) + { + if (ct.IsCancellationRequested) return; + bob.Append(c); + Text = bob.ToString(); + await Task.Delay(timePerCharacter, ct); + } + } + catch (OperationCanceledException) + { + } + } +} \ No newline at end of file diff --git a/LockIn/ViewModels/MainWindowViewModel.cs b/LockIn/ViewModels/MainWindowViewModel.cs index e59a558..9d574f9 100644 --- a/LockIn/ViewModels/MainWindowViewModel.cs +++ b/LockIn/ViewModels/MainWindowViewModel.cs @@ -23,7 +23,6 @@ public partial class MainWindowViewModel : ObservableObject private readonly Thread _gameFinder; private readonly Thread _cursorClipper; private readonly CancellationTokenSource _applicationExitTokenSource = new(); - private CancellationTokenSource _statusLabelAnimatorTokenSource = new(); public MainWindowViewModel() { @@ -67,37 +66,7 @@ private void UpdateLabel() } } - _statusLabelAnimatorTokenSource.Cancel(); - _statusLabelAnimatorTokenSource = new CancellationTokenSource(); - _ = TypeWriterEffect(value => StatusText = value, textToSet, TimeSpan.FromMilliseconds(75), _statusLabelAnimatorTokenSource.Token); - } - - private Task TypeWriterEffect(Action callback, string newText, TimeSpan timePerChar, CancellationToken ct = default) - { - try - { - return Task.Run(() => - { - ct.ThrowIfCancellationRequested(); - callback(string.Empty); - StringBuilder bob = new(newText.Length); - foreach (char c in newText) - { - ct.ThrowIfCancellationRequested(); - bob.Append(c); - callback(bob.ToString()); - Thread.Sleep(timePerChar); - } - }, ct); - } - catch (TaskCanceledException) - { - return Task.FromCanceled(ct); - } - catch (OperationCanceledException) - { - return Task.FromCanceled(ct); - } + StatusText = textToSet; } private void ThreadFindGame() @@ -161,8 +130,7 @@ private void GameExit(object? sender, EventArgs e) partial void OnIsLockEnabledChanged(bool value) { - _ = TypeWriterEffect(strValue => LockInText = strValue, value ? "Unlock" : "Lock In", - TimeSpan.FromMilliseconds(80)); + LockInText = value ? "Unlock" : "Lock In"; Natives.ClipCursor(IntPtr.Zero); UpdateLabel(); }