Skip to content

Commit 9301d8f

Browse files
committed
Add drag-and-drop import & refactor import logic
- Enable WinUI and native file drag-and-drop support for the Plugin Manager - Add drag enter/over/leave/drop handlers - Add a `DispatcherTimer` to track external drag position, and a visual drop indicator. - Wire `WindowUtility.FileDropEvent` on page load/unload and gate drops to the import area. - Extract import flow into an async `ImportPlugins` method and improve error handling around imports.
1 parent 7f35ae4 commit 9301d8f

1 file changed

Lines changed: 141 additions & 2 deletions

File tree

CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml.cs

Lines changed: 141 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
using System.Linq;
1414
using System.Threading;
1515
using System.Threading.Tasks;
16+
using Windows.ApplicationModel.DataTransfer;
17+
using Windows.Foundation;
18+
using Windows.Graphics;
19+
using Windows.Storage;
1620
// ReSharper disable CheckNamespace
1721

1822
#nullable enable
@@ -23,10 +27,36 @@ public sealed partial class PluginManagerPage
2327
public static PluginManagerPageContext Context { get; } = new();
2428
public static PluginManagerPage This { get; private set; } = null!;
2529

30+
private readonly DispatcherTimer _fileDragIndicatorTimer = new()
31+
{
32+
Interval = TimeSpan.FromMilliseconds(50)
33+
};
34+
private bool _isWinUiFileDragActive;
35+
private bool _isDropIndicatorVisible;
36+
2637
public PluginManagerPage()
2738
{
2839
This = this;
2940
InitializeComponent();
41+
_fileDragIndicatorTimer.Tick += OnFileDragIndicatorTimerTick;
42+
Loaded += OnPluginManagerPageLoaded;
43+
Unloaded += OnPluginManagerPageUnloaded;
44+
}
45+
46+
private void OnPluginManagerPageLoaded(object sender, RoutedEventArgs e)
47+
{
48+
WindowUtility.FileDropEvent += OnNativeFileDrop;
49+
WindowUtility.SetFileDropEnabled(true);
50+
_fileDragIndicatorTimer.Start();
51+
}
52+
53+
private void OnPluginManagerPageUnloaded(object sender, RoutedEventArgs e)
54+
{
55+
_fileDragIndicatorTimer.Stop();
56+
_isWinUiFileDragActive = false;
57+
SetImportDropIndicator(false);
58+
WindowUtility.SetFileDropEnabled(false);
59+
WindowUtility.FileDropEvent -= OnNativeFileDrop;
3060
}
3161

3262
private void OnListViewRightClickUpdate(object sender, RightTappedRoutedEventArgs e)
@@ -63,10 +93,8 @@ private void OnRestoreUninstalledPlugin(object sender, RoutedEventArgs e)
6393

6494
private async void OnClickImportButton(object sender, RoutedEventArgs e)
6595
{
66-
int imported = 0;
6796
try
6897
{
69-
ImportBoxButton.IsEnabled = false;
7098
Dictionary<string, string> supportedFiles = new()
7199
{
72100
{ Locale.Current.Lang?._PluginManagerPage?.FileDialogFileFilter1 ?? "", "*.zip;manifest.json" }
@@ -79,6 +107,117 @@ private async void OnClickImportButton(object sender, RoutedEventArgs e)
79107
return;
80108
}
81109

110+
await ImportPlugins(selectedFiles);
111+
}
112+
catch (Exception ex)
113+
{
114+
ErrorSender.SendException(ex.WrapPluginException("No plugin has been imported due to following error:"));
115+
}
116+
}
117+
118+
private void OnDragEnterImportBox(object sender, DragEventArgs e)
119+
{
120+
if (ImportBoxButton.IsEnabled && e.DataView.Contains(StandardDataFormats.StorageItems))
121+
{
122+
_isWinUiFileDragActive = true;
123+
SetImportDropIndicator(true);
124+
e.AcceptedOperation = DataPackageOperation.Copy;
125+
}
126+
}
127+
128+
private void OnDragLeaveImportBox(object sender, DragEventArgs e)
129+
{
130+
Point position = e.GetPosition(ImportBoxButton);
131+
if (position.X >= 0 && position.X <= ImportBoxButton.ActualWidth &&
132+
position.Y >= 0 && position.Y <= ImportBoxButton.ActualHeight)
133+
{
134+
return;
135+
}
136+
137+
_isWinUiFileDragActive = false;
138+
SetImportDropIndicator(false);
139+
}
140+
141+
private void OnDragOverImportBox(object sender, DragEventArgs e)
142+
{
143+
if (ImportBoxButton.IsEnabled && e.DataView.Contains(StandardDataFormats.StorageItems))
144+
{
145+
_isWinUiFileDragActive = true;
146+
SetImportDropIndicator(true);
147+
e.AcceptedOperation = DataPackageOperation.Copy;
148+
}
149+
}
150+
151+
private async void OnDropImportBox(object sender, DragEventArgs e)
152+
{
153+
_isWinUiFileDragActive = false;
154+
SetImportDropIndicator(false);
155+
try
156+
{
157+
IReadOnlyList<IStorageItem> storageItems = await e.DataView.GetStorageItemsAsync();
158+
string[] selectedFiles = storageItems.OfType<StorageFile>().Select(file => file.Path).ToArray();
159+
await ImportPlugins(selectedFiles);
160+
}
161+
catch (Exception ex)
162+
{
163+
ErrorSender.SendException(ex.WrapPluginException("No plugin has been imported due to following error:"));
164+
}
165+
}
166+
167+
private async void OnNativeFileDrop(string[] selectedFiles, PointInt32 dropPoint)
168+
{
169+
if (!IsPointInDropArea(dropPoint))
170+
{
171+
return;
172+
}
173+
174+
await ImportPlugins(selectedFiles);
175+
}
176+
177+
private void OnFileDragIndicatorTimerTick(object? sender, object e)
178+
{
179+
bool isExternalDragOverDropArea =
180+
WindowUtility.TryGetExternalDragPosition(out PointInt32 dragPosition) &&
181+
IsPointInDropArea(dragPosition);
182+
SetImportDropIndicator(_isWinUiFileDragActive || isExternalDragOverDropArea);
183+
}
184+
185+
private bool IsPointInDropArea(PointInt32 point)
186+
{
187+
Rect dropAreaBounds = ImportBoxButton.TransformToVisual(null)
188+
.TransformBounds(new Rect(0,
189+
0,
190+
ImportBoxButton.ActualWidth,
191+
ImportBoxButton.ActualHeight));
192+
double scaleFactor = WindowUtility.CurrentWindowMonitorScaleFactor;
193+
return point.X >= dropAreaBounds.Left * scaleFactor &&
194+
point.X <= dropAreaBounds.Right * scaleFactor &&
195+
point.Y >= dropAreaBounds.Top * scaleFactor &&
196+
point.Y <= dropAreaBounds.Bottom * scaleFactor;
197+
}
198+
199+
private void SetImportDropIndicator(bool isVisible)
200+
{
201+
if (_isDropIndicatorVisible == isVisible)
202+
{
203+
return;
204+
}
205+
206+
_isDropIndicatorVisible = isVisible;
207+
ImportDropIndicator.Opacity = isVisible ? 1 : 0;
208+
}
209+
210+
private async Task ImportPlugins(string[] selectedFiles)
211+
{
212+
if (selectedFiles.Length == 0)
213+
{
214+
return;
215+
}
216+
217+
int imported = 0;
218+
try
219+
{
220+
ImportBoxButton.IsEnabled = false;
82221
List<Exception> exceptions = [];
83222

84223
foreach (string filePath in selectedFiles)

0 commit comments

Comments
 (0)