552 lines
21 KiB
C#
552 lines
21 KiB
C#
using Microsoft.UI.Xaml;
|
|
using Microsoft.UI.Xaml.Controls;
|
|
using Microsoft.UI.Xaml.Input;
|
|
using Microsoft.UI.Xaml.Media.Animation;
|
|
using Microsoft.Win32;
|
|
using System;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using Windows.System;
|
|
using WinRT.Interop;
|
|
|
|
namespace ClipForge
|
|
{
|
|
public sealed partial class MainWindow : Window
|
|
{
|
|
private GlobalHotkeyService _hotkeyService;
|
|
private ScreenCaptureService _captureService;
|
|
private ClipLibraryService _clipLibrary;
|
|
private SettingsService _settingsService;
|
|
private ThumbnailService _thumbnailService;
|
|
private bool _isRecordingHotkey;
|
|
|
|
private delegate IntPtr WinProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
|
|
private WinProc _newWndProc;
|
|
private IntPtr _oldWndProc;
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, WinProc newProc);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
|
|
|
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
|
private static extern IntPtr LoadImage(IntPtr hInst, string lpszName,
|
|
uint uType, int cxDesired, int cyDesired, uint fuLoad);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern IntPtr SendMessage(IntPtr hWnd, uint msg,
|
|
IntPtr wParam, IntPtr lParam);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern short GetAsyncKeyState(int vk);
|
|
|
|
private const int VK_SHIFT = 0x10;
|
|
private const int VK_CONTROL = 0x11;
|
|
private const int VK_MENU = 0x12; // Alt
|
|
private const int VK_LWIN = 0x5B;
|
|
private const int GWLP_WNDPROC = -4;
|
|
private const uint WM_HOTKEY = 0x0312;
|
|
private const int GWL_EXSTYLE = -20;
|
|
private const int WS_EX_NOACTIVATE = 0x08000000;
|
|
private const int WS_EX_TOOLWINDOW = 0x00000080;
|
|
private const int WS_EX_TOPMOST = 0x00000008;
|
|
|
|
public MainWindow()
|
|
{
|
|
this.InitializeComponent();
|
|
|
|
var hwnd = WindowNative.GetWindowHandle(this);
|
|
|
|
// Set window icon
|
|
var iconPath = System.IO.Path.Combine(
|
|
AppContext.BaseDirectory, "clipforge.ico");
|
|
if (System.IO.File.Exists(iconPath))
|
|
{
|
|
var hIcon = LoadImage(IntPtr.Zero, iconPath, 1, 32, 32, 0x0010);
|
|
SendMessage(hwnd, 0x0080, new IntPtr(1), hIcon);
|
|
SendMessage(hwnd, 0x0080, new IntPtr(0), hIcon);
|
|
}
|
|
|
|
_newWndProc = WndProc;
|
|
_oldWndProc = SetWindowLongPtr(hwnd, GWLP_WNDPROC, _newWndProc);
|
|
|
|
_captureService = new ScreenCaptureService();
|
|
_clipLibrary = new ClipLibraryService();
|
|
_clipLibrary.Initialize();
|
|
|
|
_settingsService = new SettingsService();
|
|
_settingsService.Load();
|
|
|
|
_thumbnailService = new ThumbnailService();
|
|
|
|
_hotkeyService = new GlobalHotkeyService();
|
|
_hotkeyService.ClipRequested += OnClipRequested;
|
|
var mod = _settingsService.Settings.HotkeyModifiers;
|
|
var vk = _settingsService.Settings.HotkeyVirtualKey;
|
|
if (mod == 0 && vk == 0) { mod = 1; vk = 0x78; }
|
|
_hotkeyService.Initialize(hwnd, (uint)mod, (uint)vk);
|
|
|
|
ClipGrid.ItemsSource = _clipLibrary.Clips;
|
|
UpdateClipCount();
|
|
_clipLibrary.Clips.CollectionChanged += (s, e) => UpdateClipCount();
|
|
|
|
this.AppWindow.Closing += OnWindowClosing;
|
|
this.Activated += OnWindowActivated;
|
|
}
|
|
|
|
private void OnWindowClosing(
|
|
Microsoft.UI.Windowing.AppWindow sender,
|
|
Microsoft.UI.Windowing.AppWindowClosingEventArgs args)
|
|
{
|
|
args.Cancel = true;
|
|
this.AppWindow.Hide();
|
|
var app = Microsoft.UI.Xaml.Application.Current as App;
|
|
app?.TrayIcon?.SetStatus("Running in background");
|
|
}
|
|
|
|
private void UpdateClipCount()
|
|
{
|
|
ClipCountText.Text = $"{_clipLibrary.Clips.Count} clips";
|
|
}
|
|
|
|
private async void OnWindowActivated(object sender, WindowActivatedEventArgs args)
|
|
{
|
|
this.Activated -= OnWindowActivated;
|
|
await System.Threading.Tasks.Task.Delay(500);
|
|
StartCaptureAsync();
|
|
ApplySettings();
|
|
GenerateThumbnailsAsync();
|
|
}
|
|
|
|
private async void StartCaptureAsync()
|
|
{
|
|
var hwnd = WindowNative.GetWindowHandle(this);
|
|
var success = await _captureService.StartCaptureAsync(hwnd);
|
|
if (success)
|
|
await ShowToastAsync("⬡ Recording started");
|
|
else
|
|
await ShowToastAsync("❌ Failed to start recording");
|
|
}
|
|
|
|
private async void GenerateThumbnailsAsync()
|
|
{
|
|
await _thumbnailService.GenerateAllThumbnailsAsync(_clipLibrary.Clips);
|
|
}
|
|
|
|
private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
|
|
{
|
|
if (msg == WM_HOTKEY)
|
|
_hotkeyService.ProcessHotkey((int)wParam);
|
|
return CallWindowProc(_oldWndProc, hWnd, msg, wParam, lParam);
|
|
}
|
|
|
|
private async void OnClipRequested()
|
|
{
|
|
try
|
|
{
|
|
await ShowToastAsync("⬡ Saving clip...");
|
|
var savedPath = await _captureService.SaveClipAsync(
|
|
_settingsService.Settings.ClipLengthSeconds);
|
|
if (savedPath != null)
|
|
{
|
|
await ShowToastAsync("✅ Clip saved!");
|
|
var newClip = _clipLibrary.Clips.FirstOrDefault(
|
|
c => c.Path == savedPath);
|
|
if (newClip != null)
|
|
await _thumbnailService.GenerateThumbnailAsync(newClip);
|
|
}
|
|
else
|
|
{
|
|
await ShowToastAsync("❌ Failed to save clip");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await ShowToastAsync($"❌ {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void ApplySettings()
|
|
{
|
|
var s = _settingsService.Settings;
|
|
ClipLengthSlider.Value = s.ClipLengthSeconds;
|
|
QualitySlider.Value = s.VideoQuality;
|
|
ClipLengthLabel.Text = $"{s.ClipLengthSeconds} seconds";
|
|
QualityLabel.Text = $"{s.VideoQuality}%";
|
|
FramerateCombo.SelectedIndex = s.Framerate == 30 ? 0 : 1;
|
|
StartupToggle.IsOn = IsStartupEnabled();
|
|
if (HotkeyRecorderButton != null)
|
|
HotkeyRecorderButton.Content = HotkeyHelper.ToDisplayString((uint)s.HotkeyModifiers, (uint)s.HotkeyVirtualKey);
|
|
}
|
|
|
|
// --- STARTUP WITH WINDOWS ---
|
|
private const string StartupKey =
|
|
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
|
|
private const string StartupValueName = "ClipForge";
|
|
|
|
private bool IsStartupEnabled()
|
|
{
|
|
using var key = Registry.CurrentUser.OpenSubKey(StartupKey);
|
|
return key?.GetValue(StartupValueName) != null;
|
|
}
|
|
|
|
private void SetStartup(bool enable)
|
|
{
|
|
using var key = Registry.CurrentUser.OpenSubKey(StartupKey, true);
|
|
if (enable)
|
|
{
|
|
var exePath = System.Diagnostics.Process
|
|
.GetCurrentProcess().MainModule?.FileName ?? "";
|
|
key?.SetValue(StartupValueName, $"\"{exePath}\"");
|
|
}
|
|
else
|
|
{
|
|
key?.DeleteValue(StartupValueName, false);
|
|
}
|
|
}
|
|
|
|
private void StartupToggle_Toggled(object sender, RoutedEventArgs e)
|
|
{
|
|
SetStartup(StartupToggle.IsOn);
|
|
}
|
|
|
|
// --- SETTINGS ---
|
|
private void ClipLengthSlider_ValueChanged(object sender,
|
|
Microsoft.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
|
|
{
|
|
if (ClipLengthLabel == null) return;
|
|
int val = (int)e.NewValue;
|
|
ClipLengthLabel.Text = $"{val} seconds";
|
|
_settingsService.Settings.ClipLengthSeconds = val;
|
|
}
|
|
|
|
private void QualitySlider_ValueChanged(object sender,
|
|
Microsoft.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
|
|
{
|
|
if (QualityLabel == null) return;
|
|
int val = (int)e.NewValue;
|
|
QualityLabel.Text = $"{val}%";
|
|
_settingsService.Settings.VideoQuality = val;
|
|
}
|
|
|
|
private void FramerateCombo_SelectionChanged(object sender,
|
|
Microsoft.UI.Xaml.Controls.SelectionChangedEventArgs e)
|
|
{
|
|
if (_settingsService == null) return;
|
|
_settingsService.Settings.Framerate =
|
|
FramerateCombo.SelectedIndex == 0 ? 30 : 60;
|
|
}
|
|
|
|
private void SaveSettings_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
_settingsService.Save();
|
|
_ = ShowToastAsync("✅ Settings saved!");
|
|
}
|
|
|
|
// --- CUSTOM HOTKEY ---
|
|
private void HotkeyRecorderButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (_isRecordingHotkey) return;
|
|
_isRecordingHotkey = true;
|
|
HotkeyRecorderButton.Content = "Press any key...";
|
|
HotkeyRecorderButton.Focus(FocusState.Programmatic);
|
|
HotkeyRecorderButton.KeyDown += OnHotkeyCaptureKeyDown;
|
|
}
|
|
|
|
private void OnHotkeyCaptureKeyDown(object sender, KeyRoutedEventArgs e)
|
|
{
|
|
if (!_isRecordingHotkey) return;
|
|
e.Handled = true;
|
|
|
|
var key = e.Key;
|
|
if (HotkeyHelper.IsModifierKey(key))
|
|
return; // wait for a non-modifier key
|
|
|
|
uint mod = 0;
|
|
if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0) mod |= HotkeyHelper.MOD_CONTROL;
|
|
if ((GetAsyncKeyState(VK_MENU) & 0x8000) != 0) mod |= HotkeyHelper.MOD_ALT;
|
|
if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0) mod |= HotkeyHelper.MOD_SHIFT;
|
|
if ((GetAsyncKeyState(VK_LWIN) & 0x8000) != 0) mod |= HotkeyHelper.MOD_WIN;
|
|
|
|
uint vk = (uint)key;
|
|
_settingsService.Settings.HotkeyModifiers = (int)mod;
|
|
_settingsService.Settings.HotkeyVirtualKey = (int)vk;
|
|
|
|
var ok = _hotkeyService.UpdateHotkey(mod, vk);
|
|
var display = HotkeyHelper.ToDisplayString(mod, vk);
|
|
HotkeyRecorderButton.Content = ok ? display : display + " (in use?)";
|
|
|
|
HotkeyRecorderButton.KeyDown -= OnHotkeyCaptureKeyDown;
|
|
_isRecordingHotkey = false;
|
|
}
|
|
|
|
// --- NAV ---
|
|
private void NavClips_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
ClipsPage.Visibility = Visibility.Visible;
|
|
SettingsPage.Visibility = Visibility.Collapsed;
|
|
}
|
|
|
|
private void NavSettings_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
ClipsPage.Visibility = Visibility.Collapsed;
|
|
SettingsPage.Visibility = Visibility.Visible;
|
|
}
|
|
|
|
private void RecordButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (_captureService.IsCapturing)
|
|
{
|
|
_captureService.StopCapture();
|
|
RecordLabel.Text = "START";
|
|
RecordDot.Fill = new Microsoft.UI.Xaml.Media.SolidColorBrush(
|
|
Windows.UI.Color.FromArgb(255, 100, 100, 100));
|
|
}
|
|
else
|
|
{
|
|
StartCaptureAsync();
|
|
RecordLabel.Text = "CAPTURING";
|
|
RecordDot.Fill = new Microsoft.UI.Xaml.Media.SolidColorBrush(
|
|
Windows.UI.Color.FromArgb(255, 255, 71, 87));
|
|
}
|
|
}
|
|
|
|
private void OpenFolder_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
System.Diagnostics.Process.Start("explorer.exe",
|
|
_clipLibrary.ClipsDirectory);
|
|
}
|
|
|
|
private void SearchBox_TextChanged(AutoSuggestBox sender,
|
|
AutoSuggestBoxTextChangedEventArgs args)
|
|
{
|
|
var query = sender.Text.ToLower();
|
|
if (string.IsNullOrWhiteSpace(query))
|
|
ClipGrid.ItemsSource = _clipLibrary.Clips;
|
|
else
|
|
ClipGrid.ItemsSource = _clipLibrary.Clips
|
|
.Where(c => c.Title.ToLower().Contains(query))
|
|
.ToList();
|
|
}
|
|
|
|
// --- CLIP ACTIONS ---
|
|
private void TrimClip_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var button = sender as Microsoft.UI.Xaml.Controls.Button;
|
|
var clipPath = button?.Tag as string;
|
|
if (clipPath == null) return;
|
|
|
|
var trimmer = new TrimmerWindow(clipPath);
|
|
trimmer.Activate();
|
|
}
|
|
|
|
private async void DeleteClip_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var button = sender as Microsoft.UI.Xaml.Controls.Button;
|
|
var clipPath = button?.Tag as string;
|
|
if (clipPath == null) return;
|
|
|
|
var dialog = new Microsoft.UI.Xaml.Controls.ContentDialog
|
|
{
|
|
Title = "Delete Clip",
|
|
Content = "Are you sure? This cannot be undone.",
|
|
PrimaryButtonText = "Delete",
|
|
CloseButtonText = "Cancel",
|
|
DefaultButton = Microsoft.UI.Xaml.Controls.ContentDialogButton.Close,
|
|
XamlRoot = this.Content.XamlRoot
|
|
};
|
|
|
|
var result = await dialog.ShowAsync();
|
|
if (result == Microsoft.UI.Xaml.Controls.ContentDialogResult.Primary)
|
|
{
|
|
var clip = _clipLibrary.Clips.FirstOrDefault(c => c.Path == clipPath);
|
|
if (clip != null)
|
|
{
|
|
_thumbnailService.DeleteThumbnail(clip);
|
|
_clipLibrary.DeleteClip(clip);
|
|
}
|
|
}
|
|
}
|
|
|
|
private async void RenameClip_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var button = sender as Microsoft.UI.Xaml.Controls.Button;
|
|
var clipPath = button?.Tag as string;
|
|
if (clipPath == null) return;
|
|
|
|
var clip = _clipLibrary.Clips.FirstOrDefault(c => c.Path == clipPath);
|
|
if (clip == null) return;
|
|
|
|
var input = new Microsoft.UI.Xaml.Controls.TextBox
|
|
{
|
|
Text = clip.Title,
|
|
PlaceholderText = "Enter new name"
|
|
};
|
|
|
|
var dialog = new Microsoft.UI.Xaml.Controls.ContentDialog
|
|
{
|
|
Title = "Rename Clip",
|
|
Content = input,
|
|
PrimaryButtonText = "Rename",
|
|
CloseButtonText = "Cancel",
|
|
DefaultButton = Microsoft.UI.Xaml.Controls.ContentDialogButton.Primary,
|
|
XamlRoot = this.Content.XamlRoot
|
|
};
|
|
|
|
var result = await dialog.ShowAsync();
|
|
if (result == Microsoft.UI.Xaml.Controls.ContentDialogResult.Primary)
|
|
{
|
|
var newName = input.Text.Trim();
|
|
if (string.IsNullOrEmpty(newName)) return;
|
|
|
|
var dir = System.IO.Path.GetDirectoryName(clipPath)!;
|
|
var newPath = System.IO.Path.Combine(dir, newName + ".mp4");
|
|
var newThumbPath = System.IO.Path.Combine(dir, newName + ".thumb.png");
|
|
|
|
try
|
|
{
|
|
System.IO.File.Move(clipPath, newPath);
|
|
if (System.IO.File.Exists(clip.ThumbnailPath))
|
|
System.IO.File.Move(clip.ThumbnailPath, newThumbPath);
|
|
|
|
clip.Path = newPath;
|
|
clip.Title = newName;
|
|
clip.ThumbnailPath = newThumbPath;
|
|
|
|
ClipGrid.ItemsSource = null;
|
|
ClipGrid.ItemsSource = _clipLibrary.Clips;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await ShowToastAsync($"❌ {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- TOAST ---
|
|
private async System.Threading.Tasks.Task ShowToastAsync(string message)
|
|
{
|
|
var overlayWindow = new Microsoft.UI.Xaml.Window();
|
|
|
|
var textBlock = new Microsoft.UI.Xaml.Controls.TextBlock
|
|
{
|
|
Text = message,
|
|
Foreground = new Microsoft.UI.Xaml.Media.SolidColorBrush(
|
|
Windows.UI.Color.FromArgb(255, 232, 255, 71)),
|
|
FontSize = 14,
|
|
FontWeight = Microsoft.UI.Text.FontWeights.Bold,
|
|
VerticalAlignment = Microsoft.UI.Xaml.VerticalAlignment.Center
|
|
};
|
|
|
|
var border = new Microsoft.UI.Xaml.Controls.Border
|
|
{
|
|
Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(
|
|
Windows.UI.Color.FromArgb(230, 18, 18, 24)),
|
|
CornerRadius = new Microsoft.UI.Xaml.CornerRadius(10),
|
|
Padding = new Microsoft.UI.Xaml.Thickness(20, 12, 20, 12),
|
|
Child = textBlock,
|
|
RenderTransform = new Microsoft.UI.Xaml.Media.TranslateTransform { X = 40 },
|
|
Opacity = 0
|
|
};
|
|
|
|
overlayWindow.Content = border;
|
|
|
|
var hwnd = WindowNative.GetWindowHandle(overlayWindow);
|
|
var windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hwnd);
|
|
var appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);
|
|
|
|
int toastWidth = 220;
|
|
int toastHeight = 48;
|
|
int margin = 20;
|
|
|
|
var displayArea = Microsoft.UI.Windowing.DisplayArea.Primary;
|
|
int x = displayArea.WorkArea.Width - toastWidth - margin;
|
|
int y = displayArea.WorkArea.Y + margin + 40;
|
|
|
|
appWindow.MoveAndResize(new Windows.Graphics.RectInt32(x, y, toastWidth, toastHeight));
|
|
|
|
SetWindowLong(hwnd, GWL_EXSTYLE,
|
|
GetWindowLong(hwnd, GWL_EXSTYLE)
|
|
| WS_EX_NOACTIVATE
|
|
| WS_EX_TOOLWINDOW
|
|
| WS_EX_TOPMOST);
|
|
|
|
var overlayPresenter = Microsoft.UI.Windowing.OverlappedPresenter.Create();
|
|
overlayPresenter.IsAlwaysOnTop = true;
|
|
overlayPresenter.IsResizable = false;
|
|
overlayPresenter.IsMaximizable = false;
|
|
overlayPresenter.IsMinimizable = false;
|
|
overlayPresenter.SetBorderAndTitleBar(false, false);
|
|
appWindow.SetPresenter(overlayPresenter);
|
|
|
|
var titleBar = appWindow.TitleBar;
|
|
titleBar.ExtendsContentIntoTitleBar = true;
|
|
titleBar.ButtonBackgroundColor = Windows.UI.Color.FromArgb(0, 0, 0, 0);
|
|
|
|
overlayWindow.Activate();
|
|
|
|
var slideIn = new DoubleAnimation
|
|
{
|
|
From = 40,
|
|
To = 0,
|
|
Duration = new Duration(TimeSpan.FromMilliseconds(250)),
|
|
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
|
|
};
|
|
var fadeIn = new DoubleAnimation
|
|
{
|
|
From = 0,
|
|
To = 1,
|
|
Duration = new Duration(TimeSpan.FromMilliseconds(200)),
|
|
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
|
|
};
|
|
|
|
var storyboardIn = new Storyboard();
|
|
Storyboard.SetTarget(slideIn, border);
|
|
Storyboard.SetTargetProperty(slideIn, "(UIElement.RenderTransform).(TranslateTransform.X)");
|
|
Storyboard.SetTarget(fadeIn, border);
|
|
Storyboard.SetTargetProperty(fadeIn, "Opacity");
|
|
storyboardIn.Children.Add(slideIn);
|
|
storyboardIn.Children.Add(fadeIn);
|
|
storyboardIn.Begin();
|
|
|
|
await System.Threading.Tasks.Task.Delay(1500);
|
|
|
|
var slideOut = new DoubleAnimation
|
|
{
|
|
From = 0,
|
|
To = 40,
|
|
Duration = new Duration(TimeSpan.FromMilliseconds(200)),
|
|
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn }
|
|
};
|
|
var fadeOut = new DoubleAnimation
|
|
{
|
|
From = 1,
|
|
To = 0,
|
|
Duration = new Duration(TimeSpan.FromMilliseconds(200)),
|
|
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn }
|
|
};
|
|
|
|
var storyboardOut = new Storyboard();
|
|
Storyboard.SetTarget(slideOut, border);
|
|
Storyboard.SetTargetProperty(slideOut, "(UIElement.RenderTransform).(TranslateTransform.X)");
|
|
Storyboard.SetTarget(fadeOut, border);
|
|
Storyboard.SetTargetProperty(fadeOut, "Opacity");
|
|
storyboardOut.Children.Add(slideOut);
|
|
storyboardOut.Children.Add(fadeOut);
|
|
|
|
var tcs = new System.Threading.Tasks.TaskCompletionSource<bool>();
|
|
storyboardOut.Completed += (s, e) => tcs.SetResult(true);
|
|
storyboardOut.Begin();
|
|
await tcs.Task;
|
|
|
|
overlayWindow.Close();
|
|
}
|
|
}
|
|
} |