Add project files.
This commit is contained in:
500
ClipForge/MainWindow.xaml.cs
Normal file
500
ClipForge/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,500 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
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 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);
|
||||
|
||||
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;
|
||||
_hotkeyService.Initialize(hwnd);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// --- 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!");
|
||||
}
|
||||
|
||||
// --- 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user