Skip to content

Commit 64cc624

Browse files
committed
Refactor, UI/UX context menu, improve IPC, misc.
1 parent ceb283b commit 64cc624

File tree

6 files changed

+230
-108
lines changed

6 files changed

+230
-108
lines changed

src/PicView.Avalonia/Navigation/IPC.cs

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,25 @@
88
namespace PicView.Avalonia.Navigation;
99

1010
/// <summary>
11-
/// Provides inter-process communication (IPC) helper methods.
11+
/// Provides Inter-Process Communication (IPC) functionality using named pipes.
1212
/// </summary>
1313
internal static class IPC
1414
{
1515
/// <summary>
16-
/// Sends an argument to a running instance through a named pipe.
16+
/// The default name for the named pipe used by the application.
1717
/// </summary>
18-
/// <param name="arg">The argument to be sent.</param>
19-
/// <param name="pipeName">The name of the named pipe.</param>
20-
/// <returns>
21-
/// <c>true</c> if the argument is successfully sent; otherwise, <c>false</c>.
22-
/// </returns>
18+
internal const string PipeName = "PicViewPipe";
19+
20+
/// <summary>
21+
/// Sends an argument to a running instance of the application through the specified named pipe.
22+
/// </summary>
23+
/// <param name="arg">The argument to send to the running instance.</param>
24+
/// <param name="pipeName">The name of the pipe to connect to.</param>
25+
/// <returns>A task that represents the asynchronous operation. The task result is <c>true</c> if the operation completes successfully.</returns>
26+
/// <remarks>
27+
/// This method attempts to connect to a running instance of the application via the provided pipe and sends an argument.
28+
/// If the connection fails due to a timeout or other exceptions, they are caught and logged in debug mode.
29+
/// </remarks>
2330
internal static async Task<bool> SendArgumentToRunningInstance(string arg, string pipeName)
2431
{
2532
await using var pipeClient = new NamedPipeClientStream(pipeName);
@@ -32,7 +39,9 @@ internal static async Task<bool> SendArgumentToRunningInstance(string arg, strin
3239
}
3340
catch (TimeoutException)
3441
{
35-
return false;
42+
#if DEBUG
43+
Trace.WriteLine($"{nameof(SendArgumentToRunningInstance)} timeout");
44+
#endif
3645
}
3746
catch (Exception ex)
3847
{
@@ -44,10 +53,15 @@ internal static async Task<bool> SendArgumentToRunningInstance(string arg, strin
4453
}
4554

4655
/// <summary>
47-
/// Starts listening for incoming arguments through a named pipe.
56+
/// Starts listening for incoming arguments from other instances of the application through the specified named pipe.
4857
/// </summary>
49-
/// <param name="pipeName">The name of the named pipe.</param>
50-
/// <returns>A task representing the asynchronous operation.</returns>
58+
/// <param name="pipeName">The name of the pipe to listen on.</param>
59+
/// <param name="vm">The main view model to handle the received arguments.</param>
60+
/// <returns>A task that represents the asynchronous operation.</returns>
61+
/// <remarks>
62+
/// This method continuously listens for incoming connections on the specified named pipe.
63+
/// Upon receiving a connection, it reads arguments and processes them by loading the specified picture in the application's view model.
64+
/// </remarks>
5165
internal static async Task StartListeningForArguments(string pipeName, MainViewModel vm)
5266
{
5367
while (true) // Continue listening for new connections
@@ -86,4 +100,4 @@ await Dispatcher.UIThread.InvokeAsync(() =>
86100
}
87101
}
88102
}
89-
}
103+
}

src/PicView.Avalonia/Navigation/NavigationHelper.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,16 @@ await Task.Run(async () =>
187187
switch (check)
188188
{
189189
default:
190+
// Navigate to the image if it exists in the image iterator
191+
if (vm.ImageIterator is not null)
192+
{
193+
if (vm.ImageIterator.ImagePaths.Contains(check))
194+
{
195+
await vm.ImageIterator.IterateToIndex(vm.ImageIterator.ImagePaths.IndexOf(check))
196+
.ConfigureAwait(false);
197+
return;
198+
}
199+
}
190200
vm.CurrentView = vm.ImageViewer;
191201
await LoadPicFromFile(check, vm).ConfigureAwait(false);
192202
vm.IsLoading = false;

src/PicView.Avalonia/PicViewTheme/Icons.axaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,30 @@
3232
<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>
3333
<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>
3434
<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>
35+
<DrawingImage x:Key="Panel-Bottom">
36+
<DrawingGroup>
37+
<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">
38+
<GeometryDrawing.Pen>
39+
<Pen
40+
Brush="{StaticResource SecondaryTextColor}"
41+
LineCap="Round"
42+
LineJoin="Round"
43+
MiterLimit="4"
44+
Thickness="2" />
45+
</GeometryDrawing.Pen>
46+
</GeometryDrawing>
47+
<GeometryDrawing Geometry="F1 M3 15L21 15">
48+
<GeometryDrawing.Pen>
49+
<Pen
50+
Brush="{StaticResource SecondaryTextColor}"
51+
LineCap="Round"
52+
LineJoin="Round"
53+
MiterLimit="4"
54+
Thickness="2" />
55+
</GeometryDrawing.Pen>
56+
</GeometryDrawing>
57+
</DrawingGroup>
58+
</DrawingImage>
3559
<DrawingImage x:Key="StarFilledDrawingImage">
3660
<DrawingImage.Drawing>
3761
<DrawingGroup ClipGeometry="M0,0 V512 H512 V0 H0 Z">

src/PicView.Avalonia/UI/StartUpHelper.cs

Lines changed: 121 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -23,73 +23,121 @@ public static void Start(MainViewModel vm, bool settingsExists, IClassicDesktopS
2323

2424
if (!settingsExists)
2525
{
26-
// Fixes incorrect window
27-
w.Height = w.MinHeight;
28-
w.Width = w.MinWidth;
29-
30-
WindowHelper.CenterWindowOnScreen();
31-
vm.CanResize = true;
32-
vm.IsAutoFit = false;
26+
InitializeWindowForNoSettings(w, vm);
3327
}
3428
else
3529
{
36-
if (SettingsHelper.Settings.UIProperties.OpenInSameWindow)
30+
HandleWindowStartupSettings(vm, desktop, w, args);
31+
}
32+
33+
w.Show();
34+
InitializePostWindowStartup(vm, desktop, w, args, settingsExists);
35+
}
36+
37+
private static void InitializeWindowForNoSettings(Window w, MainViewModel vm)
38+
{
39+
w.Height = w.MinHeight;
40+
w.Width = w.MinWidth;
41+
WindowHelper.CenterWindowOnScreen();
42+
vm.CanResize = true;
43+
vm.IsAutoFit = false;
44+
}
45+
46+
private static void HandleWindowStartupSettings(MainViewModel vm, IClassicDesktopStyleApplicationLifetime desktop, Window w, string[] args)
47+
{
48+
if (SettingsHelper.Settings.UIProperties.OpenInSameWindow && ProcessHelper.CheckIfAnotherInstanceIsRunning())
49+
{
50+
HandleMultipleInstances(args);
51+
}
52+
53+
ApplyWindowSizeAndPosition(vm, desktop, w);
54+
55+
if (SettingsHelper.Settings.UIProperties.ShowInterface)
56+
{
57+
vm.IsTopToolbarShown = true;
58+
vm.IsBottomToolbarShown = SettingsHelper.Settings.UIProperties.ShowBottomNavBar;
59+
}
60+
}
61+
62+
private static void HandleMultipleInstances(string[] args)
63+
{
64+
if (args.Length > 1)
65+
{
66+
Task.Run(async () =>
3767
{
38-
var pipeName = "PicViewPipe";
39-
40-
if (ProcessHelper.CheckIfAnotherInstanceIsRunning())
68+
var retries = 0;
69+
while (!await IPC.SendArgumentToRunningInstance(args[1], IPC.PipeName))
4170
{
42-
if (args.Length > 1)
71+
await Task.Delay(1000);
72+
if (++retries > 20)
4373
{
44-
// Another instance is running, send arguments and exit
45-
_ = IPC.SendArgumentToRunningInstance(args[1], pipeName);
46-
Environment.Exit(0);
74+
break;
4775
}
4876
}
49-
}
50-
if (SettingsHelper.Settings.WindowProperties.Fullscreen)
51-
{
52-
WindowHelper.Fullscreen(vm, desktop);
53-
}
54-
else if (SettingsHelper.Settings.WindowProperties.Maximized)
55-
{
56-
WindowHelper.Maximize();
57-
}
58-
else if (SettingsHelper.Settings.WindowProperties.AutoFit)
59-
{
60-
desktop.MainWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
61-
vm.SizeToContent = SizeToContent.WidthAndHeight;
62-
vm.CanResize = false;
63-
vm.IsAutoFit = true;
64-
if (SettingsHelper.Settings.UIProperties.ShowInterface)
65-
{
66-
vm.IsTopToolbarShown = true;
67-
vm.IsBottomToolbarShown = SettingsHelper.Settings.UIProperties.ShowBottomNavBar;
68-
}
69-
}
70-
else
71-
{
72-
vm.CanResize = true;
73-
vm.IsAutoFit = false;
74-
WindowHelper.InitializeWindowSizeAndPosition(w);
75-
if (SettingsHelper.Settings.UIProperties.ShowInterface)
76-
{
77-
vm.IsTopToolbarShown = true;
78-
vm.IsBottomToolbarShown = SettingsHelper.Settings.UIProperties.ShowBottomNavBar;
79-
}
80-
}
77+
Environment.Exit(0);
78+
});
8179
}
80+
}
8281

83-
w.Show();
82+
private static void ApplyWindowSizeAndPosition(MainViewModel vm, IClassicDesktopStyleApplicationLifetime desktop, Window w)
83+
{
84+
if (SettingsHelper.Settings.WindowProperties.Fullscreen)
85+
{
86+
WindowHelper.Fullscreen(vm, desktop);
87+
}
88+
else if (SettingsHelper.Settings.WindowProperties.Maximized)
89+
{
90+
WindowHelper.Maximize();
91+
}
92+
else if (SettingsHelper.Settings.WindowProperties.AutoFit)
93+
{
94+
desktop.MainWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
95+
vm.SizeToContent = SizeToContent.WidthAndHeight;
96+
vm.CanResize = false;
97+
vm.IsAutoFit = true;
98+
}
99+
else
100+
{
101+
vm.CanResize = true;
102+
vm.IsAutoFit = false;
103+
WindowHelper.InitializeWindowSizeAndPosition(w);
104+
}
105+
}
106+
107+
private static void InitializePostWindowStartup(MainViewModel vm, IClassicDesktopStyleApplicationLifetime desktop, Window w, string[] args, bool settingsExists)
108+
{
84109
vm.IsLoading = true;
85110
ScreenHelper.UpdateScreenSize(w);
111+
ApplyThemeAndUISettings(vm, desktop);
112+
113+
LoadInitialContent(vm, args);
114+
115+
ValidateGallerySettings(vm, settingsExists);
116+
117+
BackgroundManager.SetBackground(vm);
118+
ColorManager.UpdateAccentColors(SettingsHelper.Settings.Theme.ColorTheme);
119+
120+
Task.Run(KeybindingsHelper.LoadKeybindings);
121+
UIHelper.AddMenus();
122+
SetWindowEventHandlers(w);
123+
Application.Current.Name = "PicView";
124+
125+
if (SettingsHelper.Settings.UIProperties.OpenInSameWindow)
126+
{
127+
_ = IPC.StartListeningForArguments(IPC.PipeName, vm);
128+
}
129+
}
130+
131+
private static void ApplyThemeAndUISettings(MainViewModel vm, IClassicDesktopStyleApplicationLifetime desktop)
132+
{
86133
if (SettingsHelper.Settings.Theme.GlassTheme)
87134
{
88135
ThemeManager.GlassThemeUpdates();
89136
}
137+
90138
UIHelper.SetControls(desktop);
91139
LanguageUpdater.UpdateLanguage(vm);
92-
140+
93141
if (SettingsHelper.Settings.Zoom.ScrollEnabled)
94142
{
95143
vm.ToggleScrollBarVisibility = ScrollBarVisibility.Visible;
@@ -105,9 +153,12 @@ public static void Start(MainViewModel vm, bool settingsExists, IClassicDesktopS
105153
{
106154
desktop.MainWindow.Topmost = true;
107155
}
156+
}
108157

158+
private static void LoadInitialContent(MainViewModel vm, string[] args)
159+
{
109160
vm.ImageViewer = new ImageViewer();
110-
161+
111162
if (args.Length > 1)
112163
{
113164
vm.CurrentView = vm.ImageViewer;
@@ -131,54 +182,49 @@ public static void Start(MainViewModel vm, bool settingsExists, IClassicDesktopS
131182
vm.CurrentView = new StartUpMenu();
132183
vm.IsLoading = false;
133184
}
185+
}
134186

187+
private static void ValidateGallerySettings(MainViewModel vm, bool settingsExists)
188+
{
135189
if (!settingsExists)
136190
{
137191
vm.GetBottomGalleryItemHeight = GalleryDefaults.DefaultBottomGalleryHeight;
138192
vm.GetFullGalleryItemHeight = GalleryDefaults.DefaultFullGalleryHeight;
139193
}
194+
140195
// Set default gallery sizes if they are out of range or upgrading from an old version
141196
if (vm.GetBottomGalleryItemHeight < vm.MinBottomGalleryItemHeight ||
142197
vm.GetBottomGalleryItemHeight > vm.MaxBottomGalleryItemHeight)
143198
{
144199
vm.GetBottomGalleryItemHeight = GalleryDefaults.DefaultBottomGalleryHeight;
145200
}
201+
146202
if (vm.GetFullGalleryItemHeight < vm.MinFullGalleryItemHeight ||
147203
vm.GetFullGalleryItemHeight > vm.MaxFullGalleryItemHeight)
148204
{
149205
vm.GetFullGalleryItemHeight = GalleryDefaults.DefaultFullGalleryHeight;
150206
}
151207

152-
if (!settingsExists)
208+
if (settingsExists)
153209
{
154-
if (string.IsNullOrWhiteSpace(SettingsHelper.Settings.Gallery.BottomGalleryStretchMode))
155-
{
156-
SettingsHelper.Settings.Gallery.BottomGalleryStretchMode = "UniformToFill";
157-
}
210+
return;
211+
}
158212

159-
if (string.IsNullOrWhiteSpace(SettingsHelper.Settings.Gallery.FullGalleryStretchMode))
160-
{
161-
SettingsHelper.Settings.Gallery.FullGalleryStretchMode = "UniformToFill";
162-
}
213+
if (string.IsNullOrWhiteSpace(SettingsHelper.Settings.Gallery.BottomGalleryStretchMode))
214+
{
215+
SettingsHelper.Settings.Gallery.BottomGalleryStretchMode = "UniformToFill";
163216
}
164-
165-
BackgroundManager.SetBackground(vm);
166-
ColorManager.UpdateAccentColors(SettingsHelper.Settings.Theme.ColorTheme);
167-
168-
Task.Run(KeybindingsHelper.LoadKeybindings);
169-
170-
UIHelper.AddMenus();
171217

218+
if (string.IsNullOrWhiteSpace(SettingsHelper.Settings.Gallery.FullGalleryStretchMode))
219+
{
220+
SettingsHelper.Settings.Gallery.FullGalleryStretchMode = "UniformToFill";
221+
}
222+
}
223+
224+
private static void SetWindowEventHandlers(Window w)
225+
{
172226
w.KeyDown += async (_, e) => await MainKeyboardShortcuts.MainWindow_KeysDownAsync(e).ConfigureAwait(false);
173227
w.KeyUp += (_, e) => MainKeyboardShortcuts.MainWindow_KeysUp(e);
174228
w.PointerPressed += async (_, e) => await MouseShortcuts.MainWindow_PointerPressed(e).ConfigureAwait(false);
175-
176-
Application.Current.Name = "PicView";
177-
178-
if (SettingsHelper.Settings.UIProperties.OpenInSameWindow)
179-
{
180-
// No other instance is running, create named pipe server
181-
_ = IPC.StartListeningForArguments("PicViewPipe", vm);
182-
}
183229
}
184-
}
230+
}

src/PicView.Avalonia/Views/BottomBar.axaml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,14 @@
8989
<Separator />
9090

9191
<!-- Toggle bottom bar -->
92-
<MenuItem
93-
Command="{CompiledBinding ToggleBottomNavBarCommand}"
94-
Header="{CompiledBinding ShowBottomToolbar,
95-
Mode=OneWay}"
96-
IsChecked="True"
97-
ToggleType="CheckBox" />
92+
<MenuItem Command="{CompiledBinding ToggleBottomNavBarCommand}" Header="{CompiledBinding ShowBottomToolbar, Mode=OneWay}">
93+
<MenuItem.Icon>
94+
<Image
95+
Height="12"
96+
Source="{StaticResource Panel-Bottom}"
97+
Width="12" />
98+
</MenuItem.Icon>
99+
</MenuItem>
98100

99101
</ContextMenu>
100102
</UserControl.ContextMenu>

0 commit comments

Comments
 (0)