Skip to content

Commit

Permalink
Refactor, UI/UX context menu, improve IPC, misc.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ruben2776 committed Oct 12, 2024
1 parent ceb283b commit 64cc624
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 108 deletions.
38 changes: 26 additions & 12 deletions src/PicView.Avalonia/Navigation/IPC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,25 @@
namespace PicView.Avalonia.Navigation;

/// <summary>
/// Provides inter-process communication (IPC) helper methods.
/// Provides Inter-Process Communication (IPC) functionality using named pipes.
/// </summary>
internal static class IPC
{
/// <summary>
/// Sends an argument to a running instance through a named pipe.
/// The default name for the named pipe used by the application.
/// </summary>
/// <param name="arg">The argument to be sent.</param>
/// <param name="pipeName">The name of the named pipe.</param>
/// <returns>
/// <c>true</c> if the argument is successfully sent; otherwise, <c>false</c>.
/// </returns>
internal const string PipeName = "PicViewPipe";

/// <summary>
/// Sends an argument to a running instance of the application through the specified named pipe.
/// </summary>
/// <param name="arg">The argument to send to the running instance.</param>
/// <param name="pipeName">The name of the pipe to connect to.</param>
/// <returns>A task that represents the asynchronous operation. The task result is <c>true</c> if the operation completes successfully.</returns>
/// <remarks>
/// This method attempts to connect to a running instance of the application via the provided pipe and sends an argument.
/// If the connection fails due to a timeout or other exceptions, they are caught and logged in debug mode.
/// </remarks>
internal static async Task<bool> SendArgumentToRunningInstance(string arg, string pipeName)
{
await using var pipeClient = new NamedPipeClientStream(pipeName);
Expand All @@ -32,7 +39,9 @@ internal static async Task<bool> SendArgumentToRunningInstance(string arg, strin
}
catch (TimeoutException)
{
return false;
#if DEBUG
Trace.WriteLine($"{nameof(SendArgumentToRunningInstance)} timeout");
#endif
}
catch (Exception ex)
{
Expand All @@ -44,10 +53,15 @@ internal static async Task<bool> SendArgumentToRunningInstance(string arg, strin
}

/// <summary>
/// Starts listening for incoming arguments through a named pipe.
/// Starts listening for incoming arguments from other instances of the application through the specified named pipe.
/// </summary>
/// <param name="pipeName">The name of the named pipe.</param>
/// <returns>A task representing the asynchronous operation.</returns>
/// <param name="pipeName">The name of the pipe to listen on.</param>
/// <param name="vm">The main view model to handle the received arguments.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
/// <remarks>
/// This method continuously listens for incoming connections on the specified named pipe.
/// Upon receiving a connection, it reads arguments and processes them by loading the specified picture in the application's view model.
/// </remarks>
internal static async Task StartListeningForArguments(string pipeName, MainViewModel vm)
{
while (true) // Continue listening for new connections
Expand Down Expand Up @@ -86,4 +100,4 @@ await Dispatcher.UIThread.InvokeAsync(() =>
}
}
}
}
}
10 changes: 10 additions & 0 deletions src/PicView.Avalonia/Navigation/NavigationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,16 @@ await Task.Run(async () =>
switch (check)
{
default:
// Navigate to the image if it exists in the image iterator
if (vm.ImageIterator is not null)
{
if (vm.ImageIterator.ImagePaths.Contains(check))
{
await vm.ImageIterator.IterateToIndex(vm.ImageIterator.ImagePaths.IndexOf(check))
.ConfigureAwait(false);
return;
}
}
vm.CurrentView = vm.ImageViewer;
await LoadPicFromFile(check, vm).ConfigureAwait(false);
vm.IsLoading = false;
Expand Down
24 changes: 24 additions & 0 deletions src/PicView.Avalonia/PicViewTheme/Icons.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,30 @@
<StreamGeometry x:Key="FlipGeometry">M192,96v64h248c4.4,0,8,3.6,8,8v240c0,4.4-3.6,8-8,8H136c-4.4,0-8-3.6-8-8v-48c0-4.4,3.6-8,8-8h248V224H192v64L64,192 L192,96z</StreamGeometry>
<StreamGeometry x:Key="CropGeometry">M488 352h-40V109.25l59.31-59.31c6.25-6.25 6.25-16.38 0-22.63L484.69 4.69c-6.25-6.25-16.38-6.25-22.63 0L402.75 64H192v96h114.75L160 306.75V24c0-13.26-10.75-24-24-24H88C74.75 0 64 10.74 64 24v40H24C10.75 64 0 74.74 0 88v48c0 13.25 10.75 24 24 24h40v264c0 13.25 10.75 24 24 24h232v-96H205.25L352 205.25V488c0 13.25 10.75 24 24 24h48c13.25 0 24-10.75 24-24v-40h40c13.25 0 24-10.75 24-24v-48c0-13.26-10.75-24-24-24z</StreamGeometry>
<StreamGeometry x:Key="SaveGeometry">M512 1536h768v-384h-768v384zm896 0h128v-896q0-14-10-38.5t-20-34.5l-281-281q-10-10-34-20t-39-10v416q0 40-28 68t-68 28h-576q-40 0-68-28t-28-68v-416h-128v1280h128v-416q0-40 28-68t68-28h832q40 0 68 28t28 68v416zm-384-928v-320q0-13-9.5-22.5t-22.5-9.5h-192q-13 0-22.5 9.5t-9.5 22.5v320q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5-9.5t9.5-22.5zm640 32v928q0 40-28 68t-68 28h-1344q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h928q40 0 88 20t76 48l280 280q28 28 48 76t20 88z</StreamGeometry>
<DrawingImage x:Key="Panel-Bottom">
<DrawingGroup>
<GeometryDrawing Geometry="F1 M3 19L3 5Q3 4.90175 3.00963 4.80397Q3.01926 4.70618 3.03843 4.60982Q3.0576 4.51345 3.08612 4.41943Q3.11464 4.32541 3.15224 4.23463Q3.18984 4.14386 3.23616 4.05721Q3.28247 3.97055 3.33706 3.88886Q3.39165 3.80716 3.45398 3.73121Q3.51631 3.65526 3.58579 3.58579Q3.65526 3.51631 3.73121 3.45398Q3.80716 3.39165 3.88886 3.33706Q3.97055 3.28247 4.05721 3.23616Q4.14386 3.18984 4.23463 3.15224Q4.32541 3.11464 4.41943 3.08612Q4.51345 3.0576 4.60982 3.03843Q4.70618 3.01926 4.80397 3.00963Q4.90175 3 5 3L19 3Q19.0983 3 19.196 3.00963Q19.2938 3.01926 19.3902 3.03843Q19.4865 3.0576 19.5806 3.08612Q19.6746 3.11464 19.7654 3.15224Q19.8561 3.18984 19.9428 3.23616Q20.0294 3.28247 20.1111 3.33706Q20.1928 3.39165 20.2688 3.45398Q20.3447 3.51631 20.4142 3.58579Q20.4837 3.65526 20.546 3.73121Q20.6084 3.80716 20.6629 3.88886Q20.7175 3.97055 20.7638 4.05721Q20.8102 4.14386 20.8478 4.23463Q20.8854 4.32541 20.9139 4.41943Q20.9424 4.51345 20.9616 4.60982Q20.9807 4.70618 20.9904 4.80397Q21 4.90175 21 5L21 19Q21 19.0983 20.9904 19.196Q20.9807 19.2938 20.9616 19.3902Q20.9424 19.4865 20.9139 19.5806Q20.8854 19.6746 20.8478 19.7654Q20.8102 19.8561 20.7638 19.9428Q20.7175 20.0294 20.6629 20.1111Q20.6084 20.1928 20.546 20.2688Q20.4837 20.3447 20.4142 20.4142Q20.3447 20.4837 20.2688 20.546Q20.1928 20.6084 20.1111 20.6629Q20.0294 20.7175 19.9428 20.7638Q19.8561 20.8102 19.7654 20.8478Q19.6746 20.8854 19.5806 20.9139Q19.4865 20.9424 19.3902 20.9616Q19.2938 20.9807 19.196 20.9904Q19.0983 21 19 21L5 21Q4.90175 21 4.80397 20.9904Q4.70618 20.9807 4.60982 20.9616Q4.51345 20.9424 4.41943 20.9139Q4.32541 20.8854 4.23463 20.8478Q4.14386 20.8102 4.05721 20.7638Q3.97055 20.7175 3.88886 20.6629Q3.80716 20.6084 3.73121 20.546Q3.65526 20.4837 3.58579 20.4142Q3.51631 20.3447 3.45398 20.2688Q3.39165 20.1928 3.33706 20.1111Q3.28247 20.0294 3.23616 19.9428Q3.18984 19.8561 3.15224 19.7654Q3.11464 19.6746 3.08612 19.5806Q3.0576 19.4865 3.03843 19.3902Q3.01926 19.2938 3.00963 19.196Q3 19.0983 3 19Z">
<GeometryDrawing.Pen>
<Pen
Brush="{StaticResource SecondaryTextColor}"
LineCap="Round"
LineJoin="Round"
MiterLimit="4"
Thickness="2" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Geometry="F1 M3 15L21 15">
<GeometryDrawing.Pen>
<Pen
Brush="{StaticResource SecondaryTextColor}"
LineCap="Round"
LineJoin="Round"
MiterLimit="4"
Thickness="2" />
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingGroup>
</DrawingImage>
<DrawingImage x:Key="StarFilledDrawingImage">
<DrawingImage.Drawing>
<DrawingGroup ClipGeometry="M0,0 V512 H512 V0 H0 Z">
Expand Down
196 changes: 121 additions & 75 deletions src/PicView.Avalonia/UI/StartUpHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,73 +23,121 @@ public static void Start(MainViewModel vm, bool settingsExists, IClassicDesktopS

if (!settingsExists)
{
// Fixes incorrect window
w.Height = w.MinHeight;
w.Width = w.MinWidth;

WindowHelper.CenterWindowOnScreen();
vm.CanResize = true;
vm.IsAutoFit = false;
InitializeWindowForNoSettings(w, vm);
}
else
{
if (SettingsHelper.Settings.UIProperties.OpenInSameWindow)
HandleWindowStartupSettings(vm, desktop, w, args);
}

w.Show();
InitializePostWindowStartup(vm, desktop, w, args, settingsExists);
}

private static void InitializeWindowForNoSettings(Window w, MainViewModel vm)
{
w.Height = w.MinHeight;
w.Width = w.MinWidth;
WindowHelper.CenterWindowOnScreen();
vm.CanResize = true;
vm.IsAutoFit = false;
}

private static void HandleWindowStartupSettings(MainViewModel vm, IClassicDesktopStyleApplicationLifetime desktop, Window w, string[] args)
{
if (SettingsHelper.Settings.UIProperties.OpenInSameWindow && ProcessHelper.CheckIfAnotherInstanceIsRunning())
{
HandleMultipleInstances(args);
}

ApplyWindowSizeAndPosition(vm, desktop, w);

if (SettingsHelper.Settings.UIProperties.ShowInterface)
{
vm.IsTopToolbarShown = true;
vm.IsBottomToolbarShown = SettingsHelper.Settings.UIProperties.ShowBottomNavBar;
}
}

private static void HandleMultipleInstances(string[] args)
{
if (args.Length > 1)
{
Task.Run(async () =>
{
var pipeName = "PicViewPipe";

if (ProcessHelper.CheckIfAnotherInstanceIsRunning())
var retries = 0;
while (!await IPC.SendArgumentToRunningInstance(args[1], IPC.PipeName))
{
if (args.Length > 1)
await Task.Delay(1000);
if (++retries > 20)
{
// Another instance is running, send arguments and exit
_ = IPC.SendArgumentToRunningInstance(args[1], pipeName);
Environment.Exit(0);
break;
}
}
}
if (SettingsHelper.Settings.WindowProperties.Fullscreen)
{
WindowHelper.Fullscreen(vm, desktop);
}
else if (SettingsHelper.Settings.WindowProperties.Maximized)
{
WindowHelper.Maximize();
}
else if (SettingsHelper.Settings.WindowProperties.AutoFit)
{
desktop.MainWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
vm.SizeToContent = SizeToContent.WidthAndHeight;
vm.CanResize = false;
vm.IsAutoFit = true;
if (SettingsHelper.Settings.UIProperties.ShowInterface)
{
vm.IsTopToolbarShown = true;
vm.IsBottomToolbarShown = SettingsHelper.Settings.UIProperties.ShowBottomNavBar;
}
}
else
{
vm.CanResize = true;
vm.IsAutoFit = false;
WindowHelper.InitializeWindowSizeAndPosition(w);
if (SettingsHelper.Settings.UIProperties.ShowInterface)
{
vm.IsTopToolbarShown = true;
vm.IsBottomToolbarShown = SettingsHelper.Settings.UIProperties.ShowBottomNavBar;
}
}
Environment.Exit(0);
});
}
}

w.Show();
private static void ApplyWindowSizeAndPosition(MainViewModel vm, IClassicDesktopStyleApplicationLifetime desktop, Window w)
{
if (SettingsHelper.Settings.WindowProperties.Fullscreen)
{
WindowHelper.Fullscreen(vm, desktop);
}
else if (SettingsHelper.Settings.WindowProperties.Maximized)
{
WindowHelper.Maximize();
}
else if (SettingsHelper.Settings.WindowProperties.AutoFit)
{
desktop.MainWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
vm.SizeToContent = SizeToContent.WidthAndHeight;
vm.CanResize = false;
vm.IsAutoFit = true;
}
else
{
vm.CanResize = true;
vm.IsAutoFit = false;
WindowHelper.InitializeWindowSizeAndPosition(w);
}
}

private static void InitializePostWindowStartup(MainViewModel vm, IClassicDesktopStyleApplicationLifetime desktop, Window w, string[] args, bool settingsExists)
{
vm.IsLoading = true;
ScreenHelper.UpdateScreenSize(w);
ApplyThemeAndUISettings(vm, desktop);

LoadInitialContent(vm, args);

ValidateGallerySettings(vm, settingsExists);

BackgroundManager.SetBackground(vm);
ColorManager.UpdateAccentColors(SettingsHelper.Settings.Theme.ColorTheme);

Task.Run(KeybindingsHelper.LoadKeybindings);
UIHelper.AddMenus();
SetWindowEventHandlers(w);
Application.Current.Name = "PicView";

if (SettingsHelper.Settings.UIProperties.OpenInSameWindow)
{
_ = IPC.StartListeningForArguments(IPC.PipeName, vm);
}
}

private static void ApplyThemeAndUISettings(MainViewModel vm, IClassicDesktopStyleApplicationLifetime desktop)
{
if (SettingsHelper.Settings.Theme.GlassTheme)
{
ThemeManager.GlassThemeUpdates();
}

UIHelper.SetControls(desktop);
LanguageUpdater.UpdateLanguage(vm);

if (SettingsHelper.Settings.Zoom.ScrollEnabled)
{
vm.ToggleScrollBarVisibility = ScrollBarVisibility.Visible;
Expand All @@ -105,9 +153,12 @@ public static void Start(MainViewModel vm, bool settingsExists, IClassicDesktopS
{
desktop.MainWindow.Topmost = true;
}
}

private static void LoadInitialContent(MainViewModel vm, string[] args)
{
vm.ImageViewer = new ImageViewer();

if (args.Length > 1)
{
vm.CurrentView = vm.ImageViewer;
Expand All @@ -131,54 +182,49 @@ public static void Start(MainViewModel vm, bool settingsExists, IClassicDesktopS
vm.CurrentView = new StartUpMenu();
vm.IsLoading = false;
}
}

private static void ValidateGallerySettings(MainViewModel vm, bool settingsExists)
{
if (!settingsExists)
{
vm.GetBottomGalleryItemHeight = GalleryDefaults.DefaultBottomGalleryHeight;
vm.GetFullGalleryItemHeight = GalleryDefaults.DefaultFullGalleryHeight;
}

// Set default gallery sizes if they are out of range or upgrading from an old version
if (vm.GetBottomGalleryItemHeight < vm.MinBottomGalleryItemHeight ||
vm.GetBottomGalleryItemHeight > vm.MaxBottomGalleryItemHeight)
{
vm.GetBottomGalleryItemHeight = GalleryDefaults.DefaultBottomGalleryHeight;
}

if (vm.GetFullGalleryItemHeight < vm.MinFullGalleryItemHeight ||
vm.GetFullGalleryItemHeight > vm.MaxFullGalleryItemHeight)
{
vm.GetFullGalleryItemHeight = GalleryDefaults.DefaultFullGalleryHeight;
}

if (!settingsExists)
if (settingsExists)
{
if (string.IsNullOrWhiteSpace(SettingsHelper.Settings.Gallery.BottomGalleryStretchMode))
{
SettingsHelper.Settings.Gallery.BottomGalleryStretchMode = "UniformToFill";
}
return;
}

if (string.IsNullOrWhiteSpace(SettingsHelper.Settings.Gallery.FullGalleryStretchMode))
{
SettingsHelper.Settings.Gallery.FullGalleryStretchMode = "UniformToFill";
}
if (string.IsNullOrWhiteSpace(SettingsHelper.Settings.Gallery.BottomGalleryStretchMode))
{
SettingsHelper.Settings.Gallery.BottomGalleryStretchMode = "UniformToFill";
}

BackgroundManager.SetBackground(vm);
ColorManager.UpdateAccentColors(SettingsHelper.Settings.Theme.ColorTheme);

Task.Run(KeybindingsHelper.LoadKeybindings);

UIHelper.AddMenus();

if (string.IsNullOrWhiteSpace(SettingsHelper.Settings.Gallery.FullGalleryStretchMode))
{
SettingsHelper.Settings.Gallery.FullGalleryStretchMode = "UniformToFill";
}
}

private static void SetWindowEventHandlers(Window w)
{
w.KeyDown += async (_, e) => await MainKeyboardShortcuts.MainWindow_KeysDownAsync(e).ConfigureAwait(false);
w.KeyUp += (_, e) => MainKeyboardShortcuts.MainWindow_KeysUp(e);
w.PointerPressed += async (_, e) => await MouseShortcuts.MainWindow_PointerPressed(e).ConfigureAwait(false);

Application.Current.Name = "PicView";

if (SettingsHelper.Settings.UIProperties.OpenInSameWindow)
{
// No other instance is running, create named pipe server
_ = IPC.StartListeningForArguments("PicViewPipe", vm);
}
}
}
}
14 changes: 8 additions & 6 deletions src/PicView.Avalonia/Views/BottomBar.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,14 @@
<Separator />

<!-- Toggle bottom bar -->
<MenuItem
Command="{CompiledBinding ToggleBottomNavBarCommand}"
Header="{CompiledBinding ShowBottomToolbar,
Mode=OneWay}"
IsChecked="True"
ToggleType="CheckBox" />
<MenuItem Command="{CompiledBinding ToggleBottomNavBarCommand}" Header="{CompiledBinding ShowBottomToolbar, Mode=OneWay}">
<MenuItem.Icon>
<Image
Height="12"
Source="{StaticResource Panel-Bottom}"
Width="12" />
</MenuItem.Icon>
</MenuItem>

</ContextMenu>
</UserControl.ContextMenu>
Expand Down
Loading

0 comments on commit 64cc624

Please sign in to comment.