mirror of
https://github.com/WinDurango/WinDurango.UI.git
synced 2026-01-31 00:55:24 +01:00
398 lines
16 KiB
C#
398 lines
16 KiB
C#
using Microsoft.UI.Xaml;
|
|
using Microsoft.UI.Xaml.Controls;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Security.Principal;
|
|
using System.Threading.Tasks;
|
|
using Windows.ApplicationModel;
|
|
using Windows.Foundation;
|
|
using Windows.Gaming.Input;
|
|
using Windows.Management.Deployment;
|
|
using Windows.Storage;
|
|
using Windows.Storage.Pickers;
|
|
using WinDurango.UI.Controls;
|
|
using WinDurango.UI.Dialogs;
|
|
using WinDurango.UI.Settings;
|
|
using WinDurango.UI.Utils;
|
|
using static WinDurango.UI.Localization.Locale;
|
|
|
|
namespace WinDurango.UI.Pages
|
|
{
|
|
public sealed partial class AppsListPage : Page
|
|
{
|
|
private Gamepad gamepad;
|
|
private int currentIndex;
|
|
private Point currentPoint = new(0, 0);
|
|
private bool inputProcessed = true;
|
|
private long lastInput;
|
|
|
|
public async Task InitAppListAsync()
|
|
{
|
|
appList.Children.Clear();
|
|
SwitchScrollDirection(App.Settings.Settings.AppViewIsHorizontalScrolling);
|
|
|
|
List<installedPackage> installedPackages = await Task.Run(() => App.InstalledPackages.GetPackages());
|
|
PackageManager pm = new();
|
|
|
|
foreach (installedPackage installedPackage in installedPackages)
|
|
{
|
|
if (this.SearchBox.Text.Length > 0)
|
|
{
|
|
// Maybe we should at some point save the package Name/DisplayName to installedPackage model too? to skip this step
|
|
Package pk = Packages.GetPackageByFamilyName(installedPackage.FamilyName);
|
|
if (pk != null)
|
|
{
|
|
string searchMatch = "";
|
|
try
|
|
{
|
|
searchMatch = pk.DisplayName ?? pk.Id.Name;
|
|
}
|
|
catch
|
|
{
|
|
searchMatch = pk.Id.Name;
|
|
}
|
|
if (searchMatch.Contains(this.SearchBox.Text, StringComparison.InvariantCultureIgnoreCase) == false) continue;
|
|
}
|
|
}
|
|
|
|
// TODO: add handling for that annoying invalid logo stuff
|
|
if (pm.FindPackageForUser(WindowsIdentity.GetCurrent().User?.Value, installedPackage.FullName) != null)
|
|
{
|
|
try
|
|
{
|
|
AppTile gameContainer = new(installedPackage.FamilyName);
|
|
|
|
this.DispatcherQueue.TryEnqueue(() =>
|
|
{
|
|
appList.Children.Add(gameContainer);
|
|
});
|
|
|
|
Logger.WriteDebug($"Added {installedPackage.FamilyName} to the app list");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// maybe should have infobar on app list?
|
|
Logger.WriteError($"Failed to add {installedPackage.FamilyName} to the app list: {ex.Message}");
|
|
Logger.WriteException(ex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Logger.WriteError($"Couldn't find package {installedPackage.FullName} in installed UWP packages list");
|
|
}
|
|
}
|
|
}
|
|
|
|
private async void ShowAppListView(object sender, RoutedEventArgs e)
|
|
{
|
|
List<Windows.ApplicationModel.Package> uwpApps = Packages.GetInstalledPackages().ToList();
|
|
if (uwpApps.Count <= 0)
|
|
{
|
|
NoticeDialog dialog = new NoticeDialog("No UWP Apps have been found.");
|
|
await dialog.ShowAsync();
|
|
return;
|
|
}
|
|
AppListDialog dl = new(uwpApps, true);
|
|
dl.Title = "Installed UWP apps";
|
|
dl.XamlRoot = this.Content.XamlRoot;
|
|
await dl.ShowAsync();
|
|
}
|
|
|
|
private async void ShowInstalledEraApps(object sender, RoutedEventArgs e)
|
|
{
|
|
List<Windows.ApplicationModel.Package> eraApps = XHandler.GetXPackages(Packages.GetInstalledPackages().ToList());
|
|
if (eraApps.Count <= 0)
|
|
{
|
|
NoticeDialog dialog = new NoticeDialog("No Era/XUWP Apps have been found.");
|
|
await dialog.ShowAsync();
|
|
return;
|
|
}
|
|
AppListDialog dl = new(eraApps, true);
|
|
dl.Title = "Installed Era/XUWP apps";
|
|
dl.XamlRoot = this.Content.XamlRoot;
|
|
await dl.ShowAsync();
|
|
}
|
|
|
|
public void SwitchScrollDirection(bool horizontal)
|
|
{
|
|
if (horizontal)
|
|
{
|
|
scrollView.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
|
|
scrollView.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
|
|
appList.Orientation = Orientation.Vertical;
|
|
appList.VerticalAlignment = VerticalAlignment.Center;
|
|
} else
|
|
{
|
|
scrollView.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
|
|
scrollView.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
|
|
appList.Orientation = Orientation.Horizontal;
|
|
appList.VerticalAlignment = VerticalAlignment.Top;
|
|
}
|
|
}
|
|
|
|
private void UpdateCheckboxes(object sender, RoutedEventArgs e)
|
|
{
|
|
if (autoSymlinkCheckBox == null || addToAppListCheckBox == null)
|
|
return;
|
|
|
|
autoSymlinkCheckBox.IsEnabled = (bool)addToAppListCheckBox.IsChecked;
|
|
}
|
|
|
|
public AppsListPage()
|
|
{
|
|
InitializeComponent();
|
|
_ = InitAppListAsync();
|
|
|
|
Loaded += OnAppListPage_Loaded;
|
|
}
|
|
|
|
private void OnAppListPage_Loaded(object sender, RoutedEventArgs e)
|
|
{
|
|
Gamepad.GamepadAdded += OnGamepadAdded;
|
|
Gamepad.GamepadRemoved += OnGamepadRemoved;
|
|
}
|
|
|
|
private void OnGamepadRemoved(object sender, Gamepad e)
|
|
{
|
|
Logger.WriteInformation("Controller disconnected");
|
|
gamepad = null;
|
|
this.DispatcherQueue.TryEnqueue(() =>
|
|
{
|
|
App.MainWindow.SwitchMode(MainWindow.AppMode.DESKTOP);
|
|
});
|
|
}
|
|
|
|
private void OnGamepadAdded(object sender, Gamepad e)
|
|
{
|
|
Logger.WriteInformation("Controller connected");
|
|
gamepad = e;
|
|
this.DispatcherQueue.TryEnqueue(() =>
|
|
{
|
|
if (appList.Children.Count > 0)
|
|
{
|
|
appList.Children[currentIndex].Focus(FocusState.Keyboard);
|
|
}
|
|
App.MainWindow.SwitchMode(MainWindow.AppMode.CONTROLLER);
|
|
});
|
|
ListenGamepadInput();
|
|
}
|
|
|
|
|
|
// can we make this work everywhere, like in content dialogs?
|
|
private async void ListenGamepadInput()
|
|
{
|
|
while (gamepad != null)
|
|
{
|
|
GamepadReading gamepadInput = gamepad.GetCurrentReading();
|
|
bool moveRight = gamepadInput.LeftThumbstickX > 0.5 || (gamepadInput.Buttons & GamepadButtons.DPadRight) != 0;
|
|
bool moveLeft = gamepadInput.LeftThumbstickX < -0.5 || (gamepadInput.Buttons & GamepadButtons.DPadLeft) != 0;
|
|
bool moveUp = gamepadInput.LeftThumbstickY > 0.5 || (gamepadInput.Buttons & GamepadButtons.DPadUp) != 0;
|
|
bool moveDown = gamepadInput.LeftThumbstickY < -0.5 || (gamepadInput.Buttons & GamepadButtons.DPadDown) != 0;
|
|
bool start = (gamepadInput.Buttons & GamepadButtons.Menu) != 0; // start as in the button, not start package
|
|
bool view = (gamepadInput.Buttons & GamepadButtons.View) != 0; // TODO: on click it should switch between the navigationview, bottom docked bar, and apps list (needs handling for other pages)
|
|
bool actionClicked = (gamepadInput.Buttons & GamepadButtons.A) != 0;
|
|
|
|
// feel like we should have like event listeners or whatever
|
|
// actionClicked += whatever
|
|
if (actionClicked && inputProcessed)
|
|
{
|
|
inputProcessed = false;
|
|
this.DispatcherQueue.TryEnqueue(() =>
|
|
{
|
|
var appTile = appList.Children[currentIndex] as AppTile;
|
|
appTile.StartApp();
|
|
inputProcessed = true;
|
|
});
|
|
}
|
|
|
|
// disabled until controller support works
|
|
// also pressing start twice will crash cuz 2 contentdialogs
|
|
//if (start && inputProcessed)
|
|
//{
|
|
// inputProcessed = false;
|
|
// this.DispatcherQueue.TryEnqueue(() =>
|
|
// {
|
|
// var appTile = appList.Children[currentIndex] as AppTile;
|
|
// appTile.ShowControllerInteractDialog();
|
|
// inputProcessed = true;
|
|
// });
|
|
//}
|
|
|
|
if ((moveRight || moveLeft || moveUp || moveDown) && inputProcessed)
|
|
{
|
|
inputProcessed = false;
|
|
if (moveRight) this.DispatcherQueue.TryEnqueue(() => MoveFocus(1, 0));
|
|
else if (moveLeft) this.DispatcherQueue.TryEnqueue(() => MoveFocus(-1, 0));
|
|
else if (moveUp) this.DispatcherQueue.TryEnqueue(() => MoveFocus(0, -1));
|
|
else if (moveDown) this.DispatcherQueue.TryEnqueue(() => MoveFocus(0, 1));
|
|
}
|
|
|
|
await Task.Delay(100);
|
|
}
|
|
}
|
|
|
|
private void MoveFocus(int xOffset, int yOffset)
|
|
{
|
|
bool firstInput = lastInput == 0;
|
|
if (lastInput > DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - 10)
|
|
{
|
|
inputProcessed = true;
|
|
return;
|
|
}
|
|
lastInput = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
|
if (appList.Children.Count == 0)
|
|
{
|
|
inputProcessed = true;
|
|
return;
|
|
}
|
|
|
|
// We only set focus first to the index 0 if ShowDevNotice was shown before as it cancels the initial focus done in Load event
|
|
if (firstInput && App.Settings.Settings.ShowDevNotice)
|
|
{
|
|
appList.Children[0].Focus(FocusState.Keyboard);
|
|
inputProcessed = true;
|
|
return;
|
|
}
|
|
|
|
int columns = GetColumnCount();
|
|
int rows = appList.Children.Count / columns;
|
|
|
|
int newX = (int)(currentPoint.X + xOffset);
|
|
int newY = (int)(currentPoint.Y + yOffset);
|
|
|
|
newX = Math.Clamp(newX, 0, columns - 1);
|
|
newY = Math.Clamp(newY, 0, rows - 1);
|
|
|
|
if (newX != currentPoint.X || newY != currentPoint.Y)
|
|
{
|
|
currentPoint = new Point(newX, newY);
|
|
currentIndex = newY * columns + newX;
|
|
|
|
if (currentIndex < appList.Children.Count)
|
|
{
|
|
appList.Children[currentIndex].Focus(FocusState.Keyboard);
|
|
}
|
|
}
|
|
|
|
inputProcessed = true;
|
|
}
|
|
|
|
// We need do this our self as WrapPanel doesn't have internal field or function to get current column amount
|
|
private int GetColumnCount()
|
|
{
|
|
if (appList.Children.Count == 0) return 1;
|
|
|
|
|
|
FrameworkElement firstItem = appList.Children[0] as FrameworkElement;
|
|
if (firstItem == null) return 1;
|
|
|
|
|
|
double firstItemTop = firstItem.TransformToVisual(appList).TransformPoint(new Point(0, 0)).Y;
|
|
int columnCount = 1;
|
|
|
|
for (int i = 1; i < appList.Children.Count; i++)
|
|
{
|
|
var item = appList.Children[i] as FrameworkElement;
|
|
if (item == null)
|
|
continue;
|
|
|
|
double itemTop = item.TransformToVisual(appList).TransformPoint(new Point(0, 0)).Y;
|
|
|
|
if (Math.Abs(itemTop - firstItemTop) < 1)
|
|
{
|
|
columnCount++;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return columnCount;
|
|
}
|
|
|
|
private void SearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
|
|
{
|
|
if (args.Reason != AutoSuggestionBoxTextChangeReason.UserInput) return;
|
|
_ = InitAppListAsync();
|
|
}
|
|
|
|
// needs to be cleaned
|
|
private async void InstallButton_Tapped(SplitButton sender, SplitButtonClickEventArgs args)
|
|
{
|
|
var picker = new FolderPicker
|
|
{
|
|
SuggestedStartLocation = PickerLocationId.Desktop
|
|
};
|
|
picker.FileTypeFilter.Add("*");
|
|
|
|
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);
|
|
|
|
WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd);
|
|
|
|
StorageFolder folder = await picker.PickSingleFolderAsync();
|
|
|
|
if (folder != null)
|
|
{
|
|
string manifest = Path.Combine(folder.Path + "\\AppxManifest.xml");
|
|
string mountFolder = Path.Combine(folder.Path + "\\Mount");
|
|
|
|
if (File.Exists(manifest))
|
|
{
|
|
var dialog = new InstallConfirmationDialog(manifest);
|
|
dialog.PrimaryButtonClick += async (sender, e) =>
|
|
{
|
|
dialog.Hide();
|
|
var controller = new ProgressDialog("Starting installation...", $"Installing {Packages.GetPropertiesFromManifest(manifest).DisplayName}", isIndeterminate: false).GetController();
|
|
_ = controller.CreateAsync(async () =>
|
|
{
|
|
await Packages.InstallPackageAsync(new Uri(manifest, UriKind.Absolute), controller,
|
|
(bool)addToAppListCheckBox.IsChecked);
|
|
});
|
|
};
|
|
await dialog.ShowAsync();
|
|
}
|
|
else
|
|
{
|
|
// AppxManifest does not exist in that folder
|
|
if (Directory.Exists(mountFolder))
|
|
{
|
|
// there IS a mount folder
|
|
if (File.Exists(Path.Combine(mountFolder + "\\AppxManifest.xml")))
|
|
{
|
|
var dialog = new InstallConfirmationDialog(Path.Combine(mountFolder + "\\AppxManifest.xml"));
|
|
dialog.PrimaryButtonClick += async (sender, e) =>
|
|
{
|
|
dialog.Hide();
|
|
var controller = new ProgressDialog("Starting installation...", "Installing", isIndeterminate: false).GetController();
|
|
_ = controller.CreateAsync(async () =>
|
|
{
|
|
await Packages.InstallXPackageAsync(folder.Path.ToString(), controller,
|
|
(bool)addToAppListCheckBox.IsChecked);
|
|
});
|
|
};
|
|
await dialog.ShowAsync();
|
|
}
|
|
else
|
|
{
|
|
// there is no AppxManifest inside.
|
|
Logger.WriteError($"Could not find AppxManifest.xml in {folder.Path} and {mountFolder}");
|
|
await new NoticeDialog(GetLocalizedText("/Packages/ManifestNotFoundMulti", folder.Path, mountFolder), "Error").ShowAsync();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Logger.WriteError($"Could not find AppxManifest.xml in {folder.Path} and no Mount folder exists");
|
|
await new NoticeDialog(GetLocalizedText("/Packages/ManifestNotFoundNoMount", folder.Path), "Error").ShowAsync();
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|