Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Pointframe.Tests/ViewModels/OverlayViewModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,15 @@ public void UpdateSizeLabel_DefaultDpi_FormatsCorrectly()
public void CopyCommand_SetsClipboardImage_AndRequestsClose()
{
var settingsMock = new Mock<IUserSettingsService>();
settingsMock.SetupGet(s => s.Current).Returns(new UserSettings { AutoSaveScreenshots = false });
settingsMock.SetupGet(s => s.Current).Returns(new UserSettings());
var clipboardMock = new Mock<IClipboardService>();
var captureMock = new Mock<IOverlayBitmapCapture>();
var bitmap = CreateBitmap();
captureMock.Setup(c => c.ComposeBitmap()).Returns(bitmap);
var vm = Vm(settingsMock, clipboardMock: clipboardMock);
var fileSystemMock = new Mock<IFileSystemService>();
fileSystemMock.Setup(f => f.OpenWrite(It.IsAny<string>())).Returns(new System.IO.MemoryStream());
fileSystemMock.Setup(f => f.CombinePath(It.IsAny<string>(), It.IsAny<string>())).Returns("snip.png");
var vm = Vm(settingsMock, clipboardMock: clipboardMock, fileSystemMock: fileSystemMock);
Comment on lines +171 to +179
var closed = false;
vm.CloseRequested += () => closed = true;
vm.SetBitmapCapture(captureMock.Object);
Expand Down
9 changes: 9 additions & 0 deletions Pointframe/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public partial class App : Application
private ICaptureLaunchService _captureLaunch = null!;
private IEventSubscription? _updateAvailableSubscription;
private IEventSubscription? _recordingCompletedSubscription;
private IEventSubscription? _captureCompletedSubscription;
private SettingsWindow? _settingsWindow;
private AboutWindow? _aboutWindow;

Expand Down Expand Up @@ -92,6 +93,7 @@ protected override void OnStartup(StartupEventArgs e)
var eventAggregator = _host.Services.GetRequiredService<IEventAggregator>();
_updateAvailableSubscription = eventAggregator.Subscribe<UpdateAvailableMessage>(HandleUpdateAvailable);
_recordingCompletedSubscription = eventAggregator.Subscribe<RecordingCompletedMessage>(HandleRecordingCompleted);
_captureCompletedSubscription = eventAggregator.Subscribe<CaptureCompletedMessage>(HandleCaptureCompleted);
_autoUpdate = _host.Services.GetRequiredService<IAutoUpdateService>();
}

Expand Down Expand Up @@ -201,6 +203,7 @@ protected override void OnExit(ExitEventArgs e)
_logger?.LogInformation("Pointframe shutting down");
_updateAvailableSubscription?.Dispose();
_recordingCompletedSubscription?.Dispose();
_captureCompletedSubscription?.Dispose();
_globalHotkey.Dispose();
_trayIconManager?.Dispose();
_host.StopAsync().GetAwaiter().GetResult();
Expand Down Expand Up @@ -377,5 +380,11 @@ private ValueTask HandleRecordingCompleted(RecordingCompletedMessage message)
_trayIconManager.HandleRecordingCompleted(message.OutputPath, message.ElapsedText);
return ValueTask.CompletedTask;
}

private ValueTask HandleCaptureCompleted(CaptureCompletedMessage message)
{
_trayIconManager.HandleCaptureCompleted(message.OutputPath);
return ValueTask.CompletedTask;
}
}

2 changes: 2 additions & 0 deletions Pointframe/Services/ITrayIconManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ internal interface ITrayIconManager : IDisposable
void HandleUpdateAvailable(Models.UpdateCheckResult result);

void HandleRecordingCompleted(string outputPath, string elapsedText);

void HandleCaptureCompleted(string outputPath);
}
3 changes: 3 additions & 0 deletions Pointframe/Services/Messaging/CaptureCompletedMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Pointframe.Services.Messaging;

public sealed record CaptureCompletedMessage(string OutputPath);
108 changes: 105 additions & 3 deletions Pointframe/Services/TrayIconManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@ internal sealed class TrayIconManager : ITrayIconManager
private readonly Action _onShowSettings;
private readonly Action _onShowAbout;

private const int MaxRecentItems = 5;

private TaskbarIcon? _trayIcon;
private WpfMenuItem? _recentRecordingsMenuItem;
private WpfMenuItem? _recentCapturesMenuItem;
private UpdateCheckResult? _pendingUpdate;
private string? _pendingRecordingBalloonPath;
private readonly List<RecentRecordingItem> _recentRecordings = [];
private readonly List<string> _recentCaptures = [];

public TrayIconManager(
ILogger<TrayIconManager> logger,
Expand Down Expand Up @@ -75,6 +79,7 @@ public void Initialize()
_trayIcon.TrayLeftMouseUp += TrayIcon_LeftClick;
_trayIcon.TrayBalloonTipClicked += OnTrayBalloonClicked;

InitializeRecentCapturesMenu();
InitializeRecentRecordingsMenu();
}

Expand All @@ -95,15 +100,27 @@ public void HandleRecordingCompleted(string outputPath, string elapsedText)
var recentRecording = new RecentRecordingItem(outputPath, elapsedText);
_recentRecordings.RemoveAll(item => string.Equals(item.OutputPath, recentRecording.OutputPath, StringComparison.OrdinalIgnoreCase));
_recentRecordings.Insert(0, recentRecording);
if (_recentRecordings.Count > 5)
if (_recentRecordings.Count > MaxRecentItems)
{
_recentRecordings.RemoveRange(5, _recentRecordings.Count - 5);
_recentRecordings.RemoveRange(MaxRecentItems, _recentRecordings.Count - MaxRecentItems);
}

RebuildRecentRecordingsMenu();
ShowRecordingCompletedBalloon(recentRecording);
}

public void HandleCaptureCompleted(string outputPath)
{
_recentCaptures.RemoveAll(p => string.Equals(p, outputPath, StringComparison.OrdinalIgnoreCase));
_recentCaptures.Insert(0, outputPath);
if (_recentCaptures.Count > MaxRecentItems)
{
_recentCaptures.RemoveRange(MaxRecentItems, _recentCaptures.Count - MaxRecentItems);
}

RebuildRecentCapturesMenu();
Comment on lines +112 to +121
}

public void AddDebugMenuItems()
{
if (_trayIcon?.ContextMenu is not { } contextMenu)
Expand Down Expand Up @@ -159,6 +176,67 @@ internal static WpfMenuItem CreateTrayMenuItem(string header, RoutedEventHandler
private void OpenImage_Click(object sender, RoutedEventArgs e) => _onOpenImage();
private void Exit_Click(object sender, RoutedEventArgs e) => WpfApplication.Current.Shutdown();

private void InitializeRecentCapturesMenu()
{
if (_trayIcon?.ContextMenu is not { } contextMenu)
{
return;
}

_recentCapturesMenuItem = new WpfMenuItem
{
Header = "Recent captures",
};

contextMenu.Items.Insert(2, _recentCapturesMenuItem);
RebuildRecentCapturesMenu();
}

private void RebuildRecentCapturesMenu()
{
if (_recentCapturesMenuItem is null)
{
return;
}

_recentCapturesMenuItem.Items.Clear();

if (_recentCaptures.Count == 0)
{
_recentCapturesMenuItem.Items.Add(new WpfMenuItem
{
Header = "No recent captures",
IsEnabled = false,
});
return;
}

foreach (var capturePath in _recentCaptures)
{
var captureItem = new WpfMenuItem
{
Header = Path.GetFileName(capturePath),
};
captureItem.Items.Add(CreateRecentCaptureActionMenuItem("Open", OpenRecentCapture_Click, capturePath));
captureItem.Items.Add(CreateRecentCaptureActionMenuItem("Open folder", OpenRecentCaptureFolder_Click, capturePath));
_recentCapturesMenuItem.Items.Add(captureItem);
}
}

private static WpfMenuItem CreateRecentCaptureActionMenuItem(
string header,
RoutedEventHandler clickHandler,
string capturePath)
{
var menuItem = new WpfMenuItem
{
Header = header,
Tag = capturePath,
};
menuItem.Click += clickHandler;
return menuItem;
}

private void InitializeRecentRecordingsMenu()
{
if (_trayIcon?.ContextMenu is not { } contextMenu)
Expand All @@ -171,7 +249,7 @@ private void InitializeRecentRecordingsMenu()
Header = "Recent recordings",
};

contextMenu.Items.Insert(2, _recentRecordingsMenuItem);
contextMenu.Items.Insert(3, _recentRecordingsMenuItem);
RebuildRecentRecordingsMenu();
}

Expand Down Expand Up @@ -221,6 +299,30 @@ private static WpfMenuItem CreateRecentRecordingActionMenuItem(
return menuItem;
}

private void OpenRecentCapture_Click(object sender, RoutedEventArgs e)
{
if (sender is not WpfMenuItem { Tag: string capturePath })
{
return;
}

OpenPath(capturePath);
}

private void OpenRecentCaptureFolder_Click(object sender, RoutedEventArgs e)
{
if (sender is not WpfMenuItem { Tag: string capturePath })
{
return;
}

var directory = Path.GetDirectoryName(capturePath);
if (!string.IsNullOrWhiteSpace(directory))
{
OpenFolder(directory);
}
}

private async void CheckForUpdates_Click(object sender, RoutedEventArgs e)
{
var menuItem = (WpfMenuItem)sender;
Expand Down
20 changes: 10 additions & 10 deletions Pointframe/ViewModels/OverlayViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public partial class OverlayViewModel : AnnotationViewModel
private readonly IDialogService _dialogService;
private readonly IFileSystemService _fileSystemService;
private readonly IUserSettingsService _settings;
private readonly IEventAggregator _eventAggregator;
private IOverlayBitmapCapture? _bitmapCapture;

public OverlayViewModel(
Expand All @@ -30,6 +31,7 @@ public OverlayViewModel(
_dialogService = dialogService;
_fileSystemService = fileSystemService;
_settings = settings;
_eventAggregator = eventAggregator;
}

public enum Phase { Selecting, Annotating }
Expand Down Expand Up @@ -105,16 +107,14 @@ private void Copy()
var finalBitmap = bitmapCapture.ComposeBitmap();
_clipboardService.SetImage(finalBitmap);

if (_settings.Current.AutoSaveScreenshots)
{
var saveDirectory = _settings.Current.ScreenshotSavePath;
_fileSystemService.CreateDirectory(saveDirectory);
var savePath = _fileSystemService.CombinePath(saveDirectory, $"Snip_{DateTime.Now:yyyyMMdd_HHmmss}.png");
using var outputStream = _fileSystemService.OpenWrite(savePath);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(finalBitmap));
encoder.Save(outputStream);
}
var saveDirectory = _settings.Current.ScreenshotSavePath;
_fileSystemService.CreateDirectory(saveDirectory);
var savePath = _fileSystemService.CombinePath(saveDirectory, $"Snip_{DateTime.Now:yyyyMMdd_HHmmss}.png");
using var outputStream = _fileSystemService.OpenWrite(savePath);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(finalBitmap));
encoder.Save(outputStream);
_ = _eventAggregator.Publish(new CaptureCompletedMessage(savePath));
Comment on lines +110 to +117

CloseRequested?.Invoke();
}
Expand Down
2 changes: 1 addition & 1 deletion version.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
"version": "5.4",
"version": "5.5",
"publicReleaseRefSpec": [
"^refs/heads/master$",
"^refs/tags/v\\d+\\.\\d+"
Expand Down
Loading