9bb4f85802
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
149 lines
5.3 KiB
C#
149 lines
5.3 KiB
C#
using Avalonia;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Input;
|
|
using Avalonia.Input.Platform;
|
|
using Avalonia.Interactivity;
|
|
using Avalonia.Layout;
|
|
using Avalonia.Platform.Storage;
|
|
using Avalonia.VisualTree;
|
|
using CVRManager.ViewModels;
|
|
|
|
namespace CVRManager.Views;
|
|
|
|
public partial class MainWindow : Window
|
|
{
|
|
public MainWindow()
|
|
{
|
|
InitializeComponent();
|
|
var vm = new MainViewModel
|
|
{
|
|
PickFolder = async () =>
|
|
{
|
|
var r = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions { AllowMultiple = false });
|
|
return r.Count > 0 ? r[0].TryGetLocalPath() : null;
|
|
},
|
|
PickFiles = async () =>
|
|
{
|
|
var r = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
|
{
|
|
AllowMultiple = true,
|
|
FileTypeFilter = new[] { new FilePickerFileType("Mod files") { Patterns = new[] { "*.dll", "*.zip" } } },
|
|
});
|
|
return (IReadOnlyList<string>)r.Select(f => f.TryGetLocalPath()).Where(p => p != null).Select(p => p!).ToList();
|
|
},
|
|
Confirm = ConfirmAsync,
|
|
CopyImage = async path =>
|
|
{
|
|
if (Clipboard == null) return;
|
|
using var bmp = new Avalonia.Media.Imaging.Bitmap(path);
|
|
await Clipboard.SetBitmapAsync(bmp);
|
|
},
|
|
};
|
|
DataContext = vm;
|
|
Opened += (_, _) => WindowIcons.Apply(this);
|
|
HookInstallDrag(vm);
|
|
}
|
|
|
|
InstallVM? dragItem;
|
|
Point dragStart;
|
|
bool dragging;
|
|
|
|
void HookInstallDrag(MainViewModel vm)
|
|
{
|
|
var list = InstallList;
|
|
list.AddHandler(PointerPressedEvent, (_, e) =>
|
|
{
|
|
if (!e.GetCurrentPoint(list).Properties.IsLeftButtonPressed) return;
|
|
if ((e.Source as Visual)?.FindAncestorOfType<Button>(true) != null) return;
|
|
if ((e.Source as StyledElement)?.DataContext is not InstallVM item) return;
|
|
dragItem = item;
|
|
dragStart = e.GetPosition(list);
|
|
dragging = false;
|
|
}, RoutingStrategies.Bubble, true);
|
|
|
|
list.AddHandler(PointerMovedEvent, (_, e) =>
|
|
{
|
|
if (dragItem == null) return;
|
|
var p = e.GetPosition(list);
|
|
if (!dragging)
|
|
{
|
|
if (Math.Abs(p.Y - dragStart.Y) < 6) return;
|
|
dragging = true;
|
|
e.Pointer.Capture(list);
|
|
list.Cursor = new Cursor(StandardCursorType.SizeNorthSouth);
|
|
}
|
|
var from = vm.Installs.IndexOf(dragItem);
|
|
for (var i = 0; i < list.ItemCount; i++)
|
|
{
|
|
if (i == from || list.ContainerFromIndex(i) is not Control c || c.TranslatePoint(default, list) is not { } top) continue;
|
|
var mid = top.Y + c.Bounds.Height / 2;
|
|
if ((i > from && p.Y > mid && p.Y < top.Y + c.Bounds.Height) || (i < from && p.Y < mid && p.Y > top.Y))
|
|
{
|
|
vm.MoveInstall(dragItem, i);
|
|
break;
|
|
}
|
|
}
|
|
MarkDragged(list);
|
|
}, RoutingStrategies.Bubble, true);
|
|
|
|
void End()
|
|
{
|
|
if (dragItem == null) return;
|
|
var moved = dragging;
|
|
dragItem = null;
|
|
dragging = false;
|
|
list.Cursor = null;
|
|
MarkDragged(list);
|
|
if (moved) vm.Save();
|
|
}
|
|
list.AddHandler(PointerReleasedEvent, (_, e) => { if (dragging) e.Pointer.Capture(null); End(); }, RoutingStrategies.Bubble, true);
|
|
list.AddHandler(PointerCaptureLostEvent, (_, _) => End(), RoutingStrategies.Bubble, true);
|
|
}
|
|
|
|
void MarkDragged(ListBox list)
|
|
{
|
|
for (var i = 0; i < list.ItemCount; i++)
|
|
if (list.ContainerFromIndex(i) is Control c)
|
|
c.Opacity = dragging && c.DataContext == dragItem ? 0.6 : 1;
|
|
}
|
|
|
|
void OnTableSelection(object? sender, SelectionChangedEventArgs e)
|
|
{
|
|
if (e.AddedItems.Count > 0 && e.AddedItems[0] is GroupHeader header)
|
|
header.ToggleCommand.Execute(null);
|
|
}
|
|
|
|
async Task<bool> ConfirmAsync(string message)
|
|
{
|
|
var dlg = new Window
|
|
{
|
|
Title = "Confirm",
|
|
Width = 460,
|
|
SizeToContent = SizeToContent.Height,
|
|
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
|
CanResize = false,
|
|
};
|
|
var ok = new Button { Content = "OK", Padding = new Thickness(18, 6) };
|
|
var cancel = new Button { Content = "Cancel", Padding = new Thickness(18, 6) };
|
|
ok.Click += (_, _) => dlg.Close(true);
|
|
cancel.Click += (_, _) => dlg.Close(false);
|
|
dlg.Content = new StackPanel
|
|
{
|
|
Margin = new Thickness(20),
|
|
Spacing = 16,
|
|
Children =
|
|
{
|
|
new TextBlock { Text = message, TextWrapping = Avalonia.Media.TextWrapping.Wrap },
|
|
new StackPanel
|
|
{
|
|
Orientation = Orientation.Horizontal,
|
|
Spacing = 8,
|
|
HorizontalAlignment = HorizontalAlignment.Right,
|
|
Children = { ok, cancel },
|
|
},
|
|
},
|
|
};
|
|
return await dlg.ShowDialog<bool>(this);
|
|
}
|
|
}
|