Compare commits
57 Commits
add_demo_g
...
1016_part3
| Author | SHA1 | Date |
|---|---|---|
|
|
0666071f79 | |
|
|
ee9660d81d | |
|
|
28b2f5709d | |
|
|
f7e8f54dbc | |
|
|
afd0dffcf6 | |
|
|
cde859c5e5 | |
|
|
757567bd15 | |
|
|
62bbf51bbc | |
|
|
c1a0da742f | |
|
|
ad0fd8be7b | |
|
|
6f2937936a | |
|
|
13b77eedcb | |
|
|
a7bfef97ac | |
|
|
10254a3c7e | |
|
|
3d65921eb7 | |
|
|
cd77a68c87 | |
|
|
a662295409 | |
|
|
4d8f5a75d5 | |
|
|
d3deab28aa | |
|
|
8c0ca1a2a7 | |
|
|
9d530f63f9 | |
|
|
f945215935 | |
|
|
c37f76f12d | |
|
|
73666f529e | |
|
|
0e55fa924c | |
|
|
d3f584a2f1 | |
|
|
dd6a936a12 | |
|
|
c0028d304b | |
|
|
ef10b4a1d8 | |
|
|
7be8a4b389 | |
|
|
1984901a31 | |
|
|
7b30868b03 | |
|
|
dc3b1d305d | |
|
|
7866bd4217 | |
|
|
30429400ed | |
|
|
78792d7a5a | |
|
|
7c08757173 | |
|
|
2a2ea5a35a | |
|
|
34e8900035 | |
|
|
923f75d340 | |
|
|
946bd0a777 | |
|
|
d31138fcca | |
|
|
077c9a940f | |
|
|
ea1dcbcea1 | |
|
|
53f233da9b | |
|
|
b368247d64 | |
|
|
c566dba5a1 | |
|
|
8d884f2692 | |
|
|
679ea0f355 | |
|
|
124dfa8afd | |
|
|
1b66707daf | |
|
|
619ac2b367 | |
|
|
a1c0d7d36d | |
|
|
6cc753e0fc | |
|
|
beedb746d9 | |
|
|
82c9731f74 | |
|
|
d708227541 |
|
|
@ -9,7 +9,6 @@
|
|||
[Aa]rtifacts/
|
||||
logs/
|
||||
.tmp/
|
||||
src/SharpEmu.Demo/
|
||||
|
||||
x86/
|
||||
x64/
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SharpEmuVersion>0.0.5-nexus-release.2</SharpEmuVersion>
|
||||
<SharpEmuVersion>0.0.5-nexus</SharpEmuVersion>
|
||||
<Version>$(SharpEmuVersion)</Version>
|
||||
|
||||
<RepoRoot>$([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)'))</RepoRoot>
|
||||
|
|
|
|||
|
|
@ -180,71 +180,4 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||
SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
|
||||
<PropertyGroup>
|
||||
<SharpEmuDemoTag>e3a6dd3</SharpEmuDemoTag>
|
||||
<SharpEmuDemoFolderName>demo</SharpEmuDemoFolderName>
|
||||
<SharpEmuDemoCacheDir>$(BaseIntermediateOutputPath)sharpemu-demo/$(SharpEmuDemoTag)</SharpEmuDemoCacheDir>
|
||||
<SharpEmuDemoArchive>$(SharpEmuDemoCacheDir)/sharpemu-demo.zip</SharpEmuDemoArchive>
|
||||
<SharpEmuDemoDir>$([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)', '..', 'SharpEmu.Demo'))</SharpEmuDemoDir>
|
||||
<SharpEmuDemoStamp>$(SharpEmuDemoDir).tag</SharpEmuDemoStamp>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="FetchSharpEmuDemo" BeforeTargets="Publish;Build">
|
||||
<ReadLinesFromFile File="$(SharpEmuDemoStamp)" Condition="Exists('$(SharpEmuDemoStamp)')">
|
||||
<Output TaskParameter="Lines" PropertyName="_SharpEmuDemoExtractedTag" />
|
||||
</ReadLinesFromFile>
|
||||
<PropertyGroup>
|
||||
<_SharpEmuDemoCurrent Condition="'$(SharpEmuDemoTag)' != '' And '$(_SharpEmuDemoExtractedTag)' == '$(SharpEmuDemoTag)'">true</_SharpEmuDemoCurrent>
|
||||
</PropertyGroup>
|
||||
<DownloadFile
|
||||
SourceUrl="https://github.com/sharpemu/sharpemu-demo/releases/download/$(SharpEmuDemoTag)/sharpemu-demo.zip"
|
||||
DestinationFolder="$(SharpEmuDemoCacheDir)"
|
||||
Condition="'$(SharpEmuDemoTag)' != '' And '$(_SharpEmuDemoCurrent)' != 'true' And !Exists('$(SharpEmuDemoArchive)')"
|
||||
ContinueOnError="WarnAndContinue" />
|
||||
<RemoveDir Directories="$(SharpEmuDemoDir)" Condition="'$(_SharpEmuDemoCurrent)' != 'true'" />
|
||||
<Unzip
|
||||
SourceFiles="$(SharpEmuDemoArchive)"
|
||||
DestinationFolder="$(SharpEmuDemoDir)"
|
||||
Condition="'$(SharpEmuDemoTag)' != '' And '$(_SharpEmuDemoCurrent)' != 'true' And Exists('$(SharpEmuDemoArchive)')"
|
||||
ContinueOnError="WarnAndContinue" />
|
||||
<PropertyGroup>
|
||||
<SharpEmuDemoComplete Condition="Exists('$(SharpEmuDemoDir)eboot.bin') And Exists('$(SharpEmuDemoDir)sce_sys/param.json') And Exists('$(SharpEmuDemoDir)sce_sys/icon0.png') And Exists('$(SharpEmuDemoDir)sce_sys/pic0.png')">true</SharpEmuDemoComplete>
|
||||
</PropertyGroup>
|
||||
<WriteLinesToFile
|
||||
File="$(SharpEmuDemoStamp)"
|
||||
Lines="$(SharpEmuDemoTag)"
|
||||
Overwrite="true"
|
||||
Condition="'$(SharpEmuDemoComplete)' == 'true' And '$(_SharpEmuDemoCurrent)' != 'true'" />
|
||||
<RemoveDir Directories="$(SharpEmuDemoDir)" Condition="'$(SharpEmuDemoComplete)' != 'true'" />
|
||||
<Delete Files="$(SharpEmuDemoArchive)" Condition="'$(SharpEmuDemoComplete)' != 'true' And Exists('$(SharpEmuDemoArchive)')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildSharpEmuDemo" AfterTargets="Build" DependsOnTargets="FetchSharpEmuDemo">
|
||||
<ItemGroup>
|
||||
<_SharpEmuDemoFiles Include="$(SharpEmuDemoDir)**/*" Exclude="$(SharpEmuDemoStamp)" Condition="'$(SharpEmuDemoComplete)' == 'true'" />
|
||||
</ItemGroup>
|
||||
<RemoveDir
|
||||
Directories="$(OutDir)$(SharpEmuDemoFolderName)"
|
||||
Condition="'$(SharpEmuDemoComplete)' != 'true'"
|
||||
ContinueOnError="WarnAndContinue" />
|
||||
<Copy
|
||||
SourceFiles="@(_SharpEmuDemoFiles)"
|
||||
DestinationFiles="@(_SharpEmuDemoFiles->'$(OutDir)$(SharpEmuDemoFolderName)/%(RecursiveDir)%(Filename)%(Extension)')"
|
||||
SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
|
||||
<Target Name="PublishSharpEmuDemo" AfterTargets="Publish" DependsOnTargets="FetchSharpEmuDemo">
|
||||
<ItemGroup>
|
||||
<_SharpEmuDemoFiles Include="$(SharpEmuDemoDir)**/*" Exclude="$(SharpEmuDemoStamp)" Condition="'$(SharpEmuDemoComplete)' == 'true'" />
|
||||
</ItemGroup>
|
||||
<RemoveDir
|
||||
Directories="$(PublishDir)$(SharpEmuDemoFolderName)"
|
||||
Condition="'$(SharpEmuDemoComplete)' != 'true'"
|
||||
ContinueOnError="WarnAndContinue" />
|
||||
<Copy
|
||||
SourceFiles="@(_SharpEmuDemoFiles)"
|
||||
DestinationFiles="@(_SharpEmuDemoFiles->'$(PublishDir)$(SharpEmuDemoFolderName)/%(RecursiveDir)%(Filename)%(Extension)')"
|
||||
SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace SharpEmu.GUI;
|
||||
|
||||
internal static class BundledDemo
|
||||
{
|
||||
internal const string FolderName = "demo";
|
||||
|
||||
private static readonly string[] RequiredFiles =
|
||||
[
|
||||
"eboot.bin",
|
||||
Path.Combine("sce_sys", "param.json"),
|
||||
Path.Combine("sce_sys", "icon0.png"),
|
||||
Path.Combine("sce_sys", "pic0.png"),
|
||||
Path.Combine("sce_sys", "snd0.at9"),
|
||||
];
|
||||
|
||||
internal static string? FindEboot(string baseDirectory)
|
||||
{
|
||||
var root = Path.Combine(baseDirectory, FolderName);
|
||||
foreach (var file in RequiredFiles)
|
||||
{
|
||||
if (!File.Exists(Path.Combine(root, file)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return Path.GetFullPath(Path.Combine(root, "eboot.bin"));
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,6 @@ namespace SharpEmu.GUI;
|
|||
public sealed class LibraryTileCollection : IReadOnlyList<LibraryTile>, IList, INotifyCollectionChanged
|
||||
{
|
||||
private readonly ObservableCollection<GameEntry> _games;
|
||||
private bool _includeAddFolderTile = true;
|
||||
|
||||
public LibraryTileCollection(ObservableCollection<GameEntry> games)
|
||||
{
|
||||
|
|
@ -25,22 +24,7 @@ public sealed class LibraryTileCollection : IReadOnlyList<LibraryTile>, IList, I
|
|||
|
||||
public event NotifyCollectionChangedEventHandler? CollectionChanged;
|
||||
|
||||
public bool IncludeAddFolderTile
|
||||
{
|
||||
get => _includeAddFolderTile;
|
||||
set
|
||||
{
|
||||
if (_includeAddFolderTile == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_includeAddFolderTile = value;
|
||||
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => _games.Count + (_includeAddFolderTile ? 1 : 0);
|
||||
public int Count => _games.Count + 1;
|
||||
|
||||
bool IList.IsFixedSize => false;
|
||||
|
||||
|
|
@ -59,7 +43,7 @@ public sealed class LibraryTileCollection : IReadOnlyList<LibraryTile>, IList, I
|
|||
return _games[index];
|
||||
}
|
||||
|
||||
if (_includeAddFolderTile && index == _games.Count)
|
||||
if (index == _games.Count)
|
||||
{
|
||||
return AddFolderTile.Instance;
|
||||
}
|
||||
|
|
@ -81,10 +65,7 @@ public sealed class LibraryTileCollection : IReadOnlyList<LibraryTile>, IList, I
|
|||
yield return game;
|
||||
}
|
||||
|
||||
if (_includeAddFolderTile)
|
||||
{
|
||||
yield return AddFolderTile.Instance;
|
||||
}
|
||||
yield return AddFolderTile.Instance;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
|
@ -98,7 +79,7 @@ public sealed class LibraryTileCollection : IReadOnlyList<LibraryTile>, IList, I
|
|||
int IList.IndexOf(object? value) => value switch
|
||||
{
|
||||
GameEntry game => _games.IndexOf(game),
|
||||
AddFolderTile when _includeAddFolderTile => _games.Count,
|
||||
AddFolderTile => _games.Count,
|
||||
_ => -1,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -299,21 +299,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||
</DataTemplate>
|
||||
</ListBox.DataTemplates>
|
||||
</ListBox>
|
||||
<Button x:Name="CarouselAddFolderButton"
|
||||
Classes="ghost"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
IsVisible="False"
|
||||
AutomationProperties.Name="{Binding [Library.AddFolder], Source={x:Static local:Localization.Instance}}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Text="+"
|
||||
FontSize="16"
|
||||
FontWeight="Light"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Text="{Binding [Library.AddFolder], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Panel>
|
||||
|
||||
<!-- Selected-game identity and actions share one detail surface so
|
||||
|
|
|
|||
|
|
@ -178,19 +178,6 @@ public partial class MainWindow : Window
|
|||
private GameEntry? _lastSelectedGame;
|
||||
private double _libraryRailRowHeight = 188;
|
||||
private double _libraryGridRowHeight = 216;
|
||||
private const double CarouselLeadingPadding = 20;
|
||||
private const double CarouselItemMargin = 12;
|
||||
private bool _carouselAnimating;
|
||||
private readonly TranslateTransform _carouselTransform = new();
|
||||
private double _carouselOffset;
|
||||
private double _carouselScrollFrom;
|
||||
private double _carouselScrollTo;
|
||||
private long _carouselScrollStartedAt;
|
||||
private bool _carouselPointerDown;
|
||||
private bool _carouselDragging;
|
||||
private Point _carouselDragStart;
|
||||
private double _carouselDragStartOffset;
|
||||
private double _carouselWheelAccumulator;
|
||||
|
||||
// Bundled key art shown whenever no game-specific backdrop applies; the
|
||||
// plain window color remains the fallback when the asset fails to load.
|
||||
|
|
@ -259,27 +246,6 @@ public partial class MainWindow : Window
|
|||
_libraryLayoutTimer.Stop();
|
||||
UpdateLibraryLayoutMetrics();
|
||||
};
|
||||
CarouselAddFolderButton.Click += (_, _) => StartAddFolderFromTile();
|
||||
GameList.AddHandler(PointerPressedEvent, OnCarouselPointerPressed, RoutingStrategies.Tunnel, handledEventsToo: true);
|
||||
GameList.AddHandler(PointerMovedEvent, OnCarouselPointerMoved, RoutingStrategies.Tunnel, handledEventsToo: true);
|
||||
GameList.AddHandler(PointerReleasedEvent, OnCarouselPointerReleased, RoutingStrategies.Tunnel, handledEventsToo: true);
|
||||
GameList.AddHandler(
|
||||
PointerCaptureLostEvent,
|
||||
(_, e) =>
|
||||
{
|
||||
if (ReferenceEquals(e.Source, GameList))
|
||||
{
|
||||
EndCarouselDrag(select: false);
|
||||
}
|
||||
},
|
||||
RoutingStrategies.Tunnel | RoutingStrategies.Bubble,
|
||||
handledEventsToo: true);
|
||||
LibraryPage.AddHandler(PointerWheelChangedEvent, OnCarouselPointerWheel, RoutingStrategies.Tunnel, handledEventsToo: true);
|
||||
GameList.SizeChanged += (_, _) =>
|
||||
{
|
||||
UpdateCarouselPadding();
|
||||
Dispatcher.UIThread.Post(() => AnchorCarouselSelection(animate: false), DispatcherPriority.Loaded);
|
||||
};
|
||||
|
||||
TitleBar.PointerPressed += OnTitleBarPointerPressed;
|
||||
TitleBar.DoubleTapped += OnTitleBarDoubleTapped;
|
||||
|
|
@ -410,7 +376,13 @@ public partial class MainWindow : Window
|
|||
Closing += (_, _) => BeginWindowClosing();
|
||||
Closed += (_, _) => CompleteWindowClosing();
|
||||
|
||||
SdlLauncherGamepad.EnsureStarted();
|
||||
_gamepadTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromMilliseconds(50),
|
||||
};
|
||||
EnvRenderDocToggle.IsCheckedChanged += (_, _) =>
|
||||
|
||||
SetEnvironmentToggle(
|
||||
"SHARPEMU_RENDERDOC",
|
||||
EnvRenderDocToggle.IsChecked == true);
|
||||
|
|
@ -418,6 +390,24 @@ public partial class MainWindow : Window
|
|||
SetEnvironmentToggle(
|
||||
"SHARPEMU_VK_DISABLE_IMPLICITS",
|
||||
EnvDisableVkImplicitLayersToggle.IsChecked == true);
|
||||
DefaultProfileBox.TextChanged += (_, _) =>
|
||||
_settings.DefaultProfile = GuiSettings.NormalizeDefaultProfile(DefaultProfileBox.Text);
|
||||
LanguageBox.SelectionChanged += (_, _) => OnLanguageChanged();
|
||||
|
||||
GameList.AddHandler(ContextRequestedEvent, OnGameContextRequested, RoutingStrategies.Tunnel);
|
||||
AddHandler(KeyDownEvent, OnPreviewKeyDown, RoutingStrategies.Tunnel);
|
||||
CtxLaunch.Click += (_, _) => LaunchSelected();
|
||||
CtxOpenFolder.Click += (_, _) => OpenSelectedGameFolder();
|
||||
CtxCopyPath.Click += async (_, _) =>
|
||||
await CopyToClipboardAsync((GameList.SelectedItem as GameEntry)?.Path);
|
||||
CtxCopyTitleId.Click += async (_, _) =>
|
||||
await CopyToClipboardAsync((GameList.SelectedItem as GameEntry)?.TitleId);
|
||||
CtxGameSettings.Click += (_, _) => OpenSelectedGameSettings();
|
||||
CtxRemove.Click += (_, _) => RemoveSelectedFromLibrary();
|
||||
|
||||
Opened += async (_, _) => await OnOpenedAsync();
|
||||
Closing += (_, _) => BeginWindowClosing();
|
||||
Closed += (_, _) => CompleteWindowClosing();
|
||||
|
||||
SdlLauncherGamepad.EnsureStarted();
|
||||
_gamepadTimer = new DispatcherTimer
|
||||
|
|
@ -502,25 +492,12 @@ public partial class MainWindow : Window
|
|||
() => SetOptionsNavigationIndicator(_optionsSectionIndex, animate: false),
|
||||
DispatcherPriority.Loaded);
|
||||
}
|
||||
else
|
||||
{
|
||||
FocusLibrarySelection();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetLibraryLayout(bool grid)
|
||||
{
|
||||
_isLibraryGridLayout = grid;
|
||||
var selectedTile = GameList.SelectedItem;
|
||||
_libraryTiles.IncludeAddFolderTile = grid;
|
||||
if (selectedTile is GameEntry && !ReferenceEquals(GameList.SelectedItem, selectedTile))
|
||||
{
|
||||
GameList.SelectedItem = selectedTile;
|
||||
}
|
||||
|
||||
CarouselAddFolderButton.IsVisible = !grid;
|
||||
SetClass(GameList, "gridLayout", grid);
|
||||
UpdateCarouselPadding();
|
||||
SetClass(LibrarySelectedDetails, "gridLayout", grid);
|
||||
LibraryPage.Margin = new Thickness(0, 6, 0, 0);
|
||||
UpdateLibraryLayoutMetrics();
|
||||
|
|
@ -529,11 +506,7 @@ public partial class MainWindow : Window
|
|||
if (GameList.SelectedItem is { } selected)
|
||||
{
|
||||
Dispatcher.UIThread.Post(
|
||||
() =>
|
||||
{
|
||||
GameList.ScrollIntoView(selected);
|
||||
AnchorCarouselSelection(animate: false);
|
||||
},
|
||||
() => GameList.ScrollIntoView(selected),
|
||||
DispatcherPriority.Loaded);
|
||||
}
|
||||
}
|
||||
|
|
@ -545,17 +518,8 @@ public partial class MainWindow : Window
|
|||
Resources["LibraryTileWidth"] = metrics.ItemWidth;
|
||||
Resources["LibraryRailItemHeight"] = metrics.RailItemHeight;
|
||||
Resources["LibraryGridItemHeight"] = metrics.GridItemHeight;
|
||||
_libraryRailRowHeight = metrics.RailItemHeight + 16 + 48;
|
||||
CarouselAddFolderButton.Margin = new Thickness(0, metrics.RailItemHeight + 24, 0, 0);
|
||||
_libraryRailRowHeight = metrics.RailItemHeight + 16;
|
||||
_libraryGridRowHeight = metrics.GridItemHeight + 16;
|
||||
UpdateCarouselPadding();
|
||||
Dispatcher.UIThread.Post(
|
||||
() =>
|
||||
{
|
||||
UpdateCarouselPadding();
|
||||
AnchorCarouselSelection(animate: false);
|
||||
},
|
||||
DispatcherPriority.Loaded);
|
||||
|
||||
GameList.MaxHeight = double.PositiveInfinity;
|
||||
UpdateLibraryMinimumHeight();
|
||||
|
|
@ -886,28 +850,11 @@ public partial class MainWindow : Window
|
|||
if (ShouldNavigate(left, ref _navLeftNextAt, now))
|
||||
{
|
||||
MoveSelection(-1);
|
||||
ReturnFocusFromCarouselAddFolder();
|
||||
}
|
||||
|
||||
if (ShouldNavigate(right, ref _navRightNextAt, now))
|
||||
{
|
||||
MoveSelection(1);
|
||||
ReturnFocusFromCarouselAddFolder();
|
||||
}
|
||||
|
||||
if (!_isLibraryGridLayout)
|
||||
{
|
||||
var up = (pad.Buttons & HostGamepadButtons.Up) != 0 || pad.LeftY < 64;
|
||||
var down = (pad.Buttons & HostGamepadButtons.Down) != 0 || pad.LeftY > 192;
|
||||
if (ShouldNavigate(down, ref _navDownNextAt, now))
|
||||
{
|
||||
CarouselAddFolderButton.Focus(NavigationMethod.Directional);
|
||||
}
|
||||
|
||||
if (ShouldNavigate(up, ref _navUpNextAt, now))
|
||||
{
|
||||
ReturnFocusFromCarouselAddFolder();
|
||||
}
|
||||
}
|
||||
|
||||
if (_isLibraryGridLayout)
|
||||
|
|
@ -930,14 +877,7 @@ public partial class MainWindow : Window
|
|||
var pressed = pad.Buttons & ~_previousPadButtons;
|
||||
if ((pressed & HostGamepadButtons.Cross) != 0)
|
||||
{
|
||||
if (!_isLibraryGridLayout && CarouselAddFolderButton.IsFocused)
|
||||
{
|
||||
StartAddFolderFromTile();
|
||||
}
|
||||
else
|
||||
{
|
||||
LaunchSelected();
|
||||
}
|
||||
LaunchSelected();
|
||||
}
|
||||
|
||||
_previousPadButtons = pad.Buttons;
|
||||
|
|
@ -1000,10 +940,6 @@ public partial class MainWindow : Window
|
|||
: Math.Clamp(GameList.SelectedIndex + delta, 0, _libraryTiles.Count - 1);
|
||||
GameList.SelectedIndex = index;
|
||||
GameList.ScrollIntoView(index);
|
||||
if (GameList.IsKeyboardFocusWithin && GameList.ContainerFromIndex(index) is { } container)
|
||||
{
|
||||
container.Focus(NavigationMethod.Unspecified);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnOpenedAsync()
|
||||
|
|
@ -1017,7 +953,6 @@ public partial class MainWindow : Window
|
|||
: $" · UNOFFICIAL {BuildInfo.CommitSha}";
|
||||
VersionText.Text = display;
|
||||
Title = $"SharpEmu {display}";
|
||||
ReportGuiRenderer();
|
||||
ToolTip.SetTip(VersionText, BuildInfo.Banner);
|
||||
|
||||
_settings = GuiSettings.Load();
|
||||
|
|
@ -1035,66 +970,7 @@ public partial class MainWindow : Window
|
|||
}
|
||||
|
||||
SeedLibraryFromCache();
|
||||
FocusLibrarySelection();
|
||||
await RescanLibraryAsync();
|
||||
FocusLibrarySelection();
|
||||
}
|
||||
|
||||
private void ReturnFocusFromCarouselAddFolder()
|
||||
{
|
||||
if (CarouselAddFolderButton.IsFocused)
|
||||
{
|
||||
FocusLibrarySelection();
|
||||
}
|
||||
}
|
||||
|
||||
private void ReportGuiRenderer()
|
||||
{
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var angle = Process.GetCurrentProcess().Modules
|
||||
.Cast<ProcessModule>()
|
||||
.Any(static module => string.Equals(module.ModuleName, "av_libglesv2.dll", StringComparison.OrdinalIgnoreCase));
|
||||
AppendConsoleLine(
|
||||
angle ? "[GUI] Renderer: GPU (ANGLE/Direct3D)" : "[GUI] Renderer: software (ANGLE not loaded)",
|
||||
angle ? DimLineBrush : WarningLineBrush);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void FocusLibrarySelection()
|
||||
{
|
||||
Dispatcher.UIThread.Post(
|
||||
() =>
|
||||
{
|
||||
if (_activePageIndex != 0 || _isGameSettingsOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameList.SelectedIndex < 0 && _visibleGames.Count > 0)
|
||||
{
|
||||
GameList.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
AnchorCarouselSelection(animate: false);
|
||||
if (GameList.SelectedIndex >= 0 && GameList.ContainerFromIndex(GameList.SelectedIndex) is { } container)
|
||||
{
|
||||
container.Focus(NavigationMethod.Unspecified);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameList.Focus(NavigationMethod.Unspecified);
|
||||
}
|
||||
},
|
||||
DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
private void PopulateLanguageBox()
|
||||
|
|
@ -2008,7 +1884,21 @@ public partial class MainWindow : Window
|
|||
continue;
|
||||
}
|
||||
|
||||
games.Add(CreateGameEntry(fullPath));
|
||||
long size = 0;
|
||||
try
|
||||
{
|
||||
size = new FileInfo(fullPath).Length;
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[GUI][WARN] Could not inspect executable '{fullPath}': {exception.Message}");
|
||||
}
|
||||
|
||||
var (title, titleId, version) = TryReadParamJson(fullPath);
|
||||
games.Add(new GameEntry(
|
||||
title ?? GameNameFor(fullPath), titleId, version, fullPath, size,
|
||||
FindCoverFor(fullPath), FindBackgroundFor(fullPath)));
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
|
|
@ -2019,41 +1909,9 @@ public partial class MainWindow : Window
|
|||
}
|
||||
|
||||
games.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase));
|
||||
AddBundledDemo(games, excludedPaths);
|
||||
return games;
|
||||
}
|
||||
|
||||
private static void AddBundledDemo(List<GameEntry> games, IReadOnlySet<string> excludedPaths)
|
||||
{
|
||||
var demo = BundledDemo.FindEboot(AppContext.BaseDirectory);
|
||||
if (demo is null || excludedPaths.Contains(demo))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
games.RemoveAll(game => string.Equals(game.Path, demo, GameLibraryPath.Comparison));
|
||||
games.Insert(0, CreateGameEntry(demo));
|
||||
}
|
||||
|
||||
private static GameEntry CreateGameEntry(string fullPath)
|
||||
{
|
||||
long size = 0;
|
||||
try
|
||||
{
|
||||
size = new FileInfo(fullPath).Length;
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[GUI][WARN] Could not inspect executable '{fullPath}': {exception.Message}");
|
||||
}
|
||||
|
||||
var (title, titleId, version) = TryReadParamJson(fullPath);
|
||||
return new GameEntry(
|
||||
title ?? GameNameFor(fullPath), titleId, version, fullPath, size,
|
||||
FindCoverFor(fullPath), FindBackgroundFor(fullPath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the game title, title id and content version from
|
||||
/// sce_sys/param.json next to the executable, when present.
|
||||
|
|
@ -2225,195 +2083,6 @@ public partial class MainWindow : Window
|
|||
|
||||
_lastSelectedGame = GameList.SelectedItem as GameEntry;
|
||||
UpdateSelectedGame();
|
||||
Dispatcher.UIThread.Post(() => AnchorCarouselSelection(animate: true), DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
private double CarouselItemStride() =>
|
||||
(Resources.TryGetValue("LibraryTileWidth", out var width) && width is double tileWidth ? tileWidth : 0) +
|
||||
CarouselItemMargin;
|
||||
|
||||
private void UpdateCarouselPadding()
|
||||
{
|
||||
if (GameList.ItemsPanelRoot is not { } panel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isLibraryGridLayout)
|
||||
{
|
||||
panel.ClearValue(MarginProperty);
|
||||
if (ReferenceEquals(panel.RenderTransform, _carouselTransform))
|
||||
{
|
||||
panel.RenderTransform = null;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
panel.Margin = new Thickness(CarouselLeadingPadding, 0, 0, 0);
|
||||
if (!ReferenceEquals(panel.RenderTransform, _carouselTransform))
|
||||
{
|
||||
panel.RenderTransform = _carouselTransform;
|
||||
}
|
||||
|
||||
SetCarouselOffset(_carouselOffset);
|
||||
}
|
||||
|
||||
private double MaximumCarouselOffset() =>
|
||||
Math.Max(0, (_visibleGames.Count - 1) * CarouselItemStride());
|
||||
|
||||
private void SetCarouselOffset(double offset)
|
||||
{
|
||||
_carouselOffset = Math.Clamp(offset, 0, MaximumCarouselOffset());
|
||||
_carouselTransform.X = -_carouselOffset;
|
||||
}
|
||||
|
||||
private void AnchorCarouselSelection(bool animate)
|
||||
{
|
||||
if (_isLibraryGridLayout || GameList.SelectedIndex < 0 || _carouselPointerDown)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var target = Math.Clamp(GameList.SelectedIndex * CarouselItemStride(), 0, MaximumCarouselOffset());
|
||||
if (!animate)
|
||||
{
|
||||
_carouselAnimating = false;
|
||||
SetCarouselOffset(target);
|
||||
return;
|
||||
}
|
||||
|
||||
_carouselScrollFrom = _carouselOffset;
|
||||
_carouselScrollTo = target;
|
||||
_carouselScrollStartedAt = Stopwatch.GetTimestamp();
|
||||
if (!_carouselAnimating)
|
||||
{
|
||||
_carouselAnimating = true;
|
||||
RequestAnimationFrame(OnCarouselAnimationFrame);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCarouselPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (_isLibraryGridLayout || !e.GetCurrentPoint(GameList).Properties.IsLeftButtonPressed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_carouselPointerDown = true;
|
||||
_carouselDragging = false;
|
||||
_carouselDragStart = e.GetPosition(GameList);
|
||||
_carouselDragStartOffset = _carouselOffset;
|
||||
}
|
||||
|
||||
private void OnCarouselPointerMoved(object? sender, PointerEventArgs e)
|
||||
{
|
||||
if (!_carouselPointerDown)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var deltaX = e.GetPosition(GameList).X - _carouselDragStart.X;
|
||||
if (!_carouselDragging)
|
||||
{
|
||||
if (Math.Abs(deltaX) < 8)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_carouselDragging = true;
|
||||
_carouselAnimating = false;
|
||||
_carouselDragStartOffset = _carouselOffset;
|
||||
_carouselDragStart = e.GetPosition(GameList);
|
||||
deltaX = 0;
|
||||
e.Pointer.Capture(GameList);
|
||||
}
|
||||
|
||||
SetCarouselOffset(_carouselDragStartOffset - deltaX);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnCarouselPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
if (!_carouselPointerDown)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dragged = _carouselDragging;
|
||||
EndCarouselDrag(select: true);
|
||||
if (dragged)
|
||||
{
|
||||
e.Pointer.Capture(null);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void EndCarouselDrag(bool select)
|
||||
{
|
||||
var dragged = _carouselDragging;
|
||||
_carouselPointerDown = false;
|
||||
_carouselDragging = false;
|
||||
if (!dragged || !select || _visibleGames.Count == 0)
|
||||
{
|
||||
if (!dragged)
|
||||
{
|
||||
AnchorCarouselSelection(animate: true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var stride = CarouselItemStride();
|
||||
var index = stride <= 0 ? 0 : (int)Math.Round(_carouselOffset / stride);
|
||||
index = Math.Clamp(index, 0, _visibleGames.Count - 1);
|
||||
if (GameList.SelectedIndex == index)
|
||||
{
|
||||
AnchorCarouselSelection(animate: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameList.SelectedIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCarouselPointerWheel(object? sender, PointerWheelEventArgs e)
|
||||
{
|
||||
if (_isLibraryGridLayout || _isGameSettingsOpen || _activePageIndex != 0 || _visibleGames.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var delta = Math.Abs(e.Delta.X) > Math.Abs(e.Delta.Y) ? -e.Delta.X : e.Delta.Y;
|
||||
_carouselWheelAccumulator += delta;
|
||||
while (Math.Abs(_carouselWheelAccumulator) >= 1)
|
||||
{
|
||||
var step = _carouselWheelAccumulator > 0 ? -1 : 1;
|
||||
_carouselWheelAccumulator += step;
|
||||
MoveSelection(step);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnCarouselAnimationFrame(TimeSpan frameTime)
|
||||
{
|
||||
if (!_carouselAnimating || _carouselDragging)
|
||||
{
|
||||
_carouselAnimating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var progress = Math.Min(1.0, Stopwatch.GetElapsedTime(_carouselScrollStartedAt).TotalMilliseconds / 260.0);
|
||||
var eased = 1 - Math.Pow(1 - progress, 3);
|
||||
SetCarouselOffset(_carouselScrollFrom + (_carouselScrollTo - _carouselScrollFrom) * eased);
|
||||
if (progress >= 1)
|
||||
{
|
||||
_carouselAnimating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
RequestAnimationFrame(OnCarouselAnimationFrame);
|
||||
}
|
||||
|
||||
private void OnGameListDoubleTapped(object? sender, TappedEventArgs e)
|
||||
|
|
|
|||
|
|
@ -9,21 +9,17 @@ Cover-art library item states and motion.
|
|||
<Style Selector="ListBox.tileGrid">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="VerticalAlignment" Value="Top" />
|
||||
<Setter Property="Margin" Value="-32,0,-32,0" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Padding" Value="4,0,8,0" />
|
||||
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
|
||||
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Disabled" />
|
||||
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Visible" />
|
||||
<Setter Property="ItemsPanel">
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" />
|
||||
<WrapPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ListBox.tileGrid.gridLayout">
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="Padding" Value="4,0,8,0" />
|
||||
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
|
||||
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Visible" />
|
||||
|
|
@ -34,7 +30,6 @@ Cover-art library item states and motion.
|
|||
</Setter>
|
||||
</Style>
|
||||
<Style Selector="ListBox.tileGrid ListBoxItem">
|
||||
<Setter Property="FocusAdorner" Value="{x:Null}" />
|
||||
<Setter Property="Width" Value="{DynamicResource LibraryTileWidth}" />
|
||||
<Setter Property="Height" Value="{DynamicResource LibraryRailItemHeight}" />
|
||||
<Setter Property="Padding" Value="4" />
|
||||
|
|
|
|||
|
|
@ -230,9 +230,7 @@ public static partial class ImageRequestBuilders
|
|||
}
|
||||
else
|
||||
{
|
||||
// A linear color buffer uses the same 256-byte row alignment as a linear texture of the same memory;
|
||||
// an unaligned pitch made the target and a later texture view of it two different cached images.
|
||||
pitch = TileGeometry.TexturePitch(transferFormat, width, GuestTileMode.Linear);
|
||||
pitch = width;
|
||||
}
|
||||
|
||||
var mipSpans = new TileLevelSpan[TiledSurfaceLayout.MaxLevels];
|
||||
|
|
|
|||
|
|
@ -240,21 +240,6 @@ public sealed class ImageRequestBuildersTests : IClassFixture<HeadlessVulkanFixt
|
|||
Assert.Equal(0u, ImageRequestBuilders.SampleCount(4));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ColorTarget_LinearTargetPitchMatchesTheLinearTexturePitch()
|
||||
{
|
||||
var target = ImageRequestBuilders.ColorTarget(RegisterWords.Color(Base, 816, 604), 0xF, 0, false);
|
||||
var texture = ImageRequestBuilders.Texture(RegisterWords.Texture(Base, GuestPixelFormat.Bits8_8_8_8UNorm, 816, 604), Sampled2D);
|
||||
|
||||
Assert.NotNull(target);
|
||||
var description = target.Value.Request.Description;
|
||||
Assert.Equal(new Extent3D(816, 604, 1), description.Extent);
|
||||
Assert.Equal(832u, description.Pitch);
|
||||
Assert.Equal(832UL * 604 * 4, description.Data.Size);
|
||||
Assert.Equal(texture.Request.Description.Pitch, description.Pitch);
|
||||
Assert.Equal(texture.Request.Description.Data.Size, description.Data.Size);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Texture_LinearTwoDimensionalFromDescriptor()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue