mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
Game selection screen + load/save state screens
This commit is contained in:
parent
bb692e90c1
commit
e008adf3a7
25 changed files with 792 additions and 141 deletions
|
@ -234,7 +234,6 @@ void NesApu::Serialize(Serializer& s)
|
|||
s.Stream(_triangleChannel.get());
|
||||
s.Stream(_noiseChannel.get());
|
||||
s.Stream(_deltaModulationChannel.get());
|
||||
s.Stream(_mixer);
|
||||
}
|
||||
|
||||
void NesApu::AddExpansionAudioDelta(AudioChannel channel, int16_t delta)
|
||||
|
|
|
@ -76,13 +76,12 @@ void NesConsole::Serialize(Serializer& s)
|
|||
s.Stream(_apu.get());
|
||||
s.Stream(_controlManager.get());
|
||||
s.Stream(_mapper.get());
|
||||
s.Stream(_mixer.get());
|
||||
|
||||
//TODO
|
||||
/*if(_hdAudioDevice) {
|
||||
_hdAudioDevice->LoadSnapshot(&loadStream, stateVersion);
|
||||
} else {
|
||||
Snapshotable::SkipBlock(&loadStream);
|
||||
}*/
|
||||
if(_hdAudioDevice) {
|
||||
//For HD packs, save the state of the bgm playback
|
||||
s.Stream(_hdAudioDevice.get());
|
||||
}
|
||||
|
||||
if(_vsSubConsole) {
|
||||
//For VS Dualsystem, the sub console's savestate is appended to the end of the file
|
||||
|
|
|
@ -20,9 +20,9 @@ EmuSettings::EmuSettings(Emulator* emu)
|
|||
|
||||
uint32_t EmuSettings::GetVersion()
|
||||
{
|
||||
//Version 0.4.0
|
||||
uint16_t major = 0;
|
||||
uint8_t minor = 4;
|
||||
//Version 2.0.0
|
||||
uint16_t major = 2;
|
||||
uint8_t minor = 0;
|
||||
uint8_t revision = 0;
|
||||
return (major << 16) | (minor << 8) | revision;
|
||||
}
|
||||
|
|
|
@ -281,10 +281,12 @@ void Emulator::Stop(bool sendNotification)
|
|||
debugger.reset();
|
||||
|
||||
_videoDecoder->StopThread();
|
||||
_videoRenderer->StopThread();
|
||||
_rewindManager.reset();
|
||||
|
||||
if(_console) {
|
||||
_console->Stop();
|
||||
_console.reset();
|
||||
}
|
||||
|
||||
_soundMixer->StopAudio(true);
|
||||
|
@ -474,6 +476,9 @@ RomInfo Emulator::GetRomInfo()
|
|||
string Emulator::GetHash(HashType type)
|
||||
{
|
||||
//TODO
|
||||
if(type == HashType::Sha1) {
|
||||
return "0000000000000000000000000000000000000000";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
@ -169,32 +169,28 @@ bool SaveStateManager::LoadState(istream &stream, bool hashCheckRequired)
|
|||
}
|
||||
|
||||
stream.read((char*)&fileFormatVersion, sizeof(fileFormatVersion));
|
||||
if(fileFormatVersion <= 5) {
|
||||
if(fileFormatVersion < SaveStateManager::MinimumSupportedVersion) {
|
||||
MessageManager::DisplayMessage("SaveStates", "SaveStateIncompatibleVersion");
|
||||
return false;
|
||||
} else {
|
||||
char hash[41] = {};
|
||||
stream.read(hash, 40);
|
||||
|
||||
if(fileFormatVersion >= 8) {
|
||||
ConsoleType consoleType;
|
||||
stream.read((char*)&consoleType, sizeof(consoleType));
|
||||
if(consoleType != _emu->GetConsoleType()) {
|
||||
MessageManager::DisplayMessage("SaveStates", "SaveStateWrongSystem");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(fileFormatVersion >= 7) {
|
||||
#ifndef LIBRETRO
|
||||
vector<uint8_t> frameData;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
if(GetScreenshotData(frameData, width, height, stream)) {
|
||||
_emu->GetVideoDecoder()->UpdateFrame((uint16_t*)frameData.data(), width, height, 0, true, true);
|
||||
}
|
||||
#endif
|
||||
ConsoleType consoleType;
|
||||
stream.read((char*)&consoleType, sizeof(consoleType));
|
||||
if(consoleType != _emu->GetConsoleType()) {
|
||||
MessageManager::DisplayMessage("SaveStates", "SaveStateWrongSystem");
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef LIBRETRO
|
||||
vector<uint8_t> frameData;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
if(GetScreenshotData(frameData, width, height, stream)) {
|
||||
_emu->GetVideoDecoder()->UpdateFrame((uint16_t*)frameData.data(), width, height, 0, true, true);
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t nameLength = 0;
|
||||
stream.read((char*)&nameLength, sizeof(uint32_t));
|
||||
|
@ -295,17 +291,16 @@ void SaveStateManager::LoadRecentGame(string filename, bool resetGame)
|
|||
std::getline(romInfoStream, romPath);
|
||||
std::getline(romInfoStream, patchPath);
|
||||
|
||||
_emu->Lock();
|
||||
try {
|
||||
if(_emu->LoadRom(romPath, patchPath)) {
|
||||
if(!resetGame) {
|
||||
auto lock = _emu->AcquireLock();
|
||||
SaveStateManager::LoadState(stateStream, false);
|
||||
}
|
||||
}
|
||||
} catch(std::exception&) {
|
||||
_emu->Stop(true);
|
||||
}
|
||||
_emu->Unlock();
|
||||
}
|
||||
|
||||
int32_t SaveStateManager::GetSaveStatePreview(string saveStatePath, uint8_t* pngData)
|
||||
|
@ -328,12 +323,12 @@ int32_t SaveStateManager::GetSaveStatePreview(string saveStatePath, uint8_t* png
|
|||
|
||||
uint32_t fileFormatVersion = 0;
|
||||
stream.read((char*)&fileFormatVersion, sizeof(fileFormatVersion));
|
||||
if(fileFormatVersion <= 6) {
|
||||
if(fileFormatVersion < SaveStateManager::MinimumSupportedVersion) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
//Skip some header fields
|
||||
stream.seekg(40, ios::cur);
|
||||
stream.seekg(44, ios::cur);
|
||||
|
||||
vector<uint8_t> frameData;
|
||||
uint32_t width = 0;
|
||||
|
|
|
@ -16,7 +16,8 @@ private:
|
|||
bool GetScreenshotData(vector<uint8_t>& out, uint32_t& width, uint32_t& height, istream& stream);
|
||||
|
||||
public:
|
||||
static constexpr uint32_t FileFormatVersion = 8;
|
||||
static constexpr uint32_t FileFormatVersion = 1;
|
||||
static constexpr uint32_t MinimumSupportedVersion = 1;
|
||||
|
||||
SaveStateManager(Emulator* emu);
|
||||
|
||||
|
|
|
@ -100,95 +100,148 @@ void ShortcutKeyHandler::ProcessRunSingleFrame()
|
|||
_emu->PauseOnNextFrame();
|
||||
}
|
||||
|
||||
bool ShortcutKeyHandler::IsShortcutAllowed(EmulatorShortcut shortcut)
|
||||
{
|
||||
bool isRunning = _emu->IsRunning();
|
||||
bool isNetplayClient = GameClient::Connected();
|
||||
bool isMoviePlaying = _emu->GetMovieManager()->Playing();
|
||||
bool isMovieRecording = _emu->GetMovieManager()->Recording();
|
||||
bool isMovieActive = isMoviePlaying || isMovieRecording;
|
||||
|
||||
switch(shortcut) {
|
||||
case EmulatorShortcut::ToggleRewind:
|
||||
case EmulatorShortcut::Rewind:
|
||||
case EmulatorShortcut::RewindTenSecs:
|
||||
case EmulatorShortcut::RewindOneMin:
|
||||
return isRunning && !isNetplayClient && !isMovieRecording;
|
||||
|
||||
case EmulatorShortcut::IncreaseSpeed:
|
||||
case EmulatorShortcut::DecreaseSpeed:
|
||||
case EmulatorShortcut::MaxSpeed:
|
||||
return !isNetplayClient;
|
||||
|
||||
case EmulatorShortcut::Reset:
|
||||
case EmulatorShortcut::PowerCycle:
|
||||
case EmulatorShortcut::ReloadRom:
|
||||
return isRunning && !isNetplayClient && !isMoviePlaying;
|
||||
|
||||
case EmulatorShortcut::PowerOff:
|
||||
return isRunning && !isNetplayClient;
|
||||
|
||||
case EmulatorShortcut::TakeScreenshot:
|
||||
return isRunning;
|
||||
|
||||
case EmulatorShortcut::ToggleCheats:
|
||||
return !isNetplayClient && !isMovieActive;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShortcutKeyHandler::ProcessShortcutPressed(EmulatorShortcut shortcut)
|
||||
{
|
||||
EmuSettings* settings = _emu->GetSettings();
|
||||
|
||||
switch(shortcut) {
|
||||
case EmulatorShortcut::Pause:
|
||||
if(_emu->IsPaused()) {
|
||||
_emu->Resume();
|
||||
} else {
|
||||
_emu->Pause();
|
||||
}
|
||||
break;
|
||||
|
||||
case EmulatorShortcut::Reset: _emu->Reset(); break;
|
||||
case EmulatorShortcut::PowerCycle: _emu->PowerCycle(); break;
|
||||
case EmulatorShortcut::ReloadRom: _emu->ReloadRom(false); break;
|
||||
case EmulatorShortcut::PowerOff: _emu->Stop(true); break;
|
||||
|
||||
case EmulatorShortcut::FastForward: settings->SetFlag(EmulationFlags::Turbo); break;
|
||||
case EmulatorShortcut::ToggleFastForward:
|
||||
if(settings->CheckFlag(EmulationFlags::Turbo)) {
|
||||
settings->ClearFlag(EmulationFlags::Turbo);
|
||||
} else {
|
||||
settings->SetFlag(EmulationFlags::Turbo);
|
||||
}
|
||||
break;
|
||||
|
||||
case EmulatorShortcut::SelectSaveSlot1: case EmulatorShortcut::SelectSaveSlot2: case EmulatorShortcut::SelectSaveSlot3: case EmulatorShortcut::SelectSaveSlot4: case EmulatorShortcut::SelectSaveSlot5:
|
||||
case EmulatorShortcut::SelectSaveSlot6: case EmulatorShortcut::SelectSaveSlot7: case EmulatorShortcut::SelectSaveSlot8: case EmulatorShortcut::SelectSaveSlot9: case EmulatorShortcut::SelectSaveSlot10:
|
||||
_emu->GetSaveStateManager()->SelectSaveSlot((int)shortcut - (int)EmulatorShortcut::SelectSaveSlot1 + 1);
|
||||
break;
|
||||
|
||||
case EmulatorShortcut::SaveStateSlot1: case EmulatorShortcut::SaveStateSlot2: case EmulatorShortcut::SaveStateSlot3: case EmulatorShortcut::SaveStateSlot4: case EmulatorShortcut::SaveStateSlot5:
|
||||
case EmulatorShortcut::SaveStateSlot6: case EmulatorShortcut::SaveStateSlot7: case EmulatorShortcut::SaveStateSlot8: case EmulatorShortcut::SaveStateSlot9: case EmulatorShortcut::SaveStateSlot10:
|
||||
_emu->GetSaveStateManager()->SaveState((int)shortcut - (int)EmulatorShortcut::SaveStateSlot1 + 1);
|
||||
break;
|
||||
|
||||
case EmulatorShortcut::LoadStateSlot1: case EmulatorShortcut::LoadStateSlot2: case EmulatorShortcut::LoadStateSlot3: case EmulatorShortcut::LoadStateSlot4: case EmulatorShortcut::LoadStateSlot5:
|
||||
case EmulatorShortcut::LoadStateSlot6: case EmulatorShortcut::LoadStateSlot7: case EmulatorShortcut::LoadStateSlot8: case EmulatorShortcut::LoadStateSlot9: case EmulatorShortcut::LoadStateSlot10:
|
||||
_emu->GetSaveStateManager()->LoadState((int)shortcut - (int)EmulatorShortcut::LoadStateSlot1 + 1);
|
||||
break;
|
||||
|
||||
case EmulatorShortcut::MoveToNextStateSlot: _emu->GetSaveStateManager()->MoveToNextSlot(); break;
|
||||
case EmulatorShortcut::MoveToPreviousStateSlot: _emu->GetSaveStateManager()->MoveToPreviousSlot(); break;
|
||||
case EmulatorShortcut::SaveState: _emu->GetSaveStateManager()->SaveState(); break;
|
||||
case EmulatorShortcut::LoadState: _emu->GetSaveStateManager()->LoadState(); break;
|
||||
|
||||
case EmulatorShortcut::RunSingleFrame: ProcessRunSingleFrame(); break;
|
||||
|
||||
case EmulatorShortcut::ToggleRewind:
|
||||
if(_emu->GetRewindManager()->IsRewinding()) {
|
||||
_emu->GetRewindManager()->StopRewinding();
|
||||
} else {
|
||||
_emu->GetRewindManager()->StartRewinding();
|
||||
}
|
||||
break;
|
||||
|
||||
case EmulatorShortcut::Rewind: _emu->GetRewindManager()->StartRewinding(); break;
|
||||
case EmulatorShortcut::RewindTenSecs: _emu->GetRewindManager()->RewindSeconds(10); break;
|
||||
case EmulatorShortcut::RewindOneMin: _emu->GetRewindManager()->RewindSeconds(60); break;
|
||||
|
||||
default:
|
||||
//Anything else is managed by the UI
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ShortcutKeyHandler::ProcessShortcutReleased(EmulatorShortcut shortcut)
|
||||
{
|
||||
EmuSettings* settings = _emu->GetSettings();
|
||||
switch(shortcut) {
|
||||
case EmulatorShortcut::FastForward: settings->ClearFlag(EmulationFlags::Turbo); break;
|
||||
case EmulatorShortcut::Rewind: _emu->GetRewindManager()->StopRewinding(); break;
|
||||
|
||||
case EmulatorShortcut::RunSingleFrame:
|
||||
_runSingleFrameRepeatTimer.reset();
|
||||
_repeatStarted = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ShortcutKeyHandler::CheckMappedKeys()
|
||||
{
|
||||
EmuSettings* settings = _emu->GetSettings();
|
||||
bool isNetplayClient = GameClient::Connected();
|
||||
bool isMovieActive = _emu->GetMovieManager()->Playing() || _emu->GetMovieManager()->Recording();
|
||||
bool isMovieRecording = _emu->GetMovieManager()->Recording();
|
||||
|
||||
//Let the UI handle these shortcuts
|
||||
for(uint64_t i = (uint64_t)EmulatorShortcut::TakeScreenshot; i < (uint64_t)EmulatorShortcut::ShortcutCount; i++) {
|
||||
if(DetectKeyPress((EmulatorShortcut)i)) {
|
||||
for(uint64_t i = 0; i < (uint64_t)EmulatorShortcut::ShortcutCount; i++) {
|
||||
EmulatorShortcut shortcut = (EmulatorShortcut)i;
|
||||
if(DetectKeyPress(shortcut)) {
|
||||
if(!IsShortcutAllowed(shortcut)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ExecuteShortcutParams params = {};
|
||||
params.Shortcut = (EmulatorShortcut)i;
|
||||
params.Shortcut = shortcut;
|
||||
_emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::ExecuteShortcut, ¶ms);
|
||||
} else if(DetectKeyRelease((EmulatorShortcut)i)) {
|
||||
|
||||
ProcessShortcutPressed(shortcut);
|
||||
} else if(DetectKeyRelease(shortcut)) {
|
||||
ExecuteShortcutParams params = {};
|
||||
params.Shortcut = (EmulatorShortcut)i;
|
||||
params.Shortcut = shortcut;
|
||||
_emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::ReleaseShortcut, ¶ms);
|
||||
}
|
||||
}
|
||||
|
||||
if(DetectKeyPress(EmulatorShortcut::FastForward)) {
|
||||
settings->SetFlag(EmulationFlags::Turbo);
|
||||
} else if(DetectKeyRelease(EmulatorShortcut::FastForward)) {
|
||||
settings->ClearFlag(EmulationFlags::Turbo);
|
||||
}
|
||||
|
||||
if(DetectKeyPress(EmulatorShortcut::ToggleFastForward)) {
|
||||
if(settings->CheckFlag(EmulationFlags::Turbo)) {
|
||||
settings->ClearFlag(EmulationFlags::Turbo);
|
||||
} else {
|
||||
settings->SetFlag(EmulationFlags::Turbo);
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = 0; i < 10; i++) {
|
||||
if(DetectKeyPress((EmulatorShortcut)((int)EmulatorShortcut::SelectSaveSlot1 + i))) {
|
||||
_emu->GetSaveStateManager()->SelectSaveSlot(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if(DetectKeyPress(EmulatorShortcut::MoveToNextStateSlot)) {
|
||||
_emu->GetSaveStateManager()->MoveToNextSlot();
|
||||
}
|
||||
|
||||
if(DetectKeyPress(EmulatorShortcut::MoveToPreviousStateSlot)) {
|
||||
_emu->GetSaveStateManager()->MoveToPreviousSlot();
|
||||
}
|
||||
|
||||
if(DetectKeyPress(EmulatorShortcut::SaveState)) {
|
||||
_emu->GetSaveStateManager()->SaveState();
|
||||
}
|
||||
|
||||
if(DetectKeyPress(EmulatorShortcut::LoadState)) {
|
||||
_emu->GetSaveStateManager()->LoadState();
|
||||
}
|
||||
|
||||
if(DetectKeyPress(EmulatorShortcut::ToggleCheats) && !isNetplayClient && !isMovieActive) {
|
||||
_emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::ExecuteShortcut, (void*)EmulatorShortcut::ToggleCheats);
|
||||
}
|
||||
|
||||
if(DetectKeyPress(EmulatorShortcut::RunSingleFrame)) {
|
||||
ProcessRunSingleFrame();
|
||||
}
|
||||
|
||||
if(DetectKeyRelease(EmulatorShortcut::RunSingleFrame)) {
|
||||
_runSingleFrameRepeatTimer.reset();
|
||||
_repeatStarted = false;
|
||||
}
|
||||
|
||||
if(!isNetplayClient && !isMovieRecording) {
|
||||
shared_ptr<RewindManager> rewindManager = _emu->GetRewindManager();
|
||||
if(rewindManager) {
|
||||
if(DetectKeyPress(EmulatorShortcut::ToggleRewind)) {
|
||||
if(rewindManager->IsRewinding()) {
|
||||
rewindManager->StopRewinding();
|
||||
} else {
|
||||
rewindManager->StartRewinding();
|
||||
}
|
||||
}
|
||||
|
||||
if(DetectKeyPress(EmulatorShortcut::Rewind)) {
|
||||
rewindManager->StartRewinding();
|
||||
} else if(DetectKeyRelease(EmulatorShortcut::Rewind)) {
|
||||
rewindManager->StopRewinding();
|
||||
} else if(DetectKeyPress(EmulatorShortcut::RewindTenSecs)) {
|
||||
rewindManager->RewindSeconds(10);
|
||||
} else if(DetectKeyPress(EmulatorShortcut::RewindOneMin)) {
|
||||
rewindManager->RewindSeconds(60);
|
||||
}
|
||||
ProcessShortcutReleased(shortcut);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,11 @@ private:
|
|||
|
||||
void ProcessRunSingleFrame();
|
||||
|
||||
bool IsShortcutAllowed(EmulatorShortcut shortcut);
|
||||
|
||||
void ProcessShortcutPressed(EmulatorShortcut shortcut);
|
||||
void ProcessShortcutReleased(EmulatorShortcut shortcut);
|
||||
|
||||
public:
|
||||
ShortcutKeyHandler(shared_ptr<Emulator> emu);
|
||||
~ShortcutKeyHandler();
|
||||
|
|
|
@ -4,6 +4,7 @@ using Avalonia.Input;
|
|||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Styling;
|
||||
using Mesen.Interop;
|
||||
using Mesen.Utilities;
|
||||
using Mesen.Windows;
|
||||
using System;
|
||||
|
||||
|
@ -42,7 +43,7 @@ namespace Mesen.Controls
|
|||
GetKeyWindow wnd = new GetKeyWindow();
|
||||
wnd.SingleKeyMode = true;
|
||||
wnd.WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
await wnd.ShowDialog(this.VisualRoot as Window);
|
||||
await wnd.ShowCenteredDialog((Window)this.VisualRoot);
|
||||
this.KeyBinding = wnd.ShortcutKey.Key1;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ using Avalonia.Input;
|
|||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Styling;
|
||||
using Mesen.Config.Shortcuts;
|
||||
using Mesen.Utilities;
|
||||
using Mesen.Windows;
|
||||
using System;
|
||||
|
||||
|
@ -42,7 +43,7 @@ namespace Mesen.Controls
|
|||
GetKeyWindow wnd = new GetKeyWindow();
|
||||
wnd.SingleKeyMode = false;
|
||||
wnd.WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
await wnd.ShowDialog(this.VisualRoot as Window);
|
||||
await wnd.ShowCenteredDialog((Window)this.VisualRoot);
|
||||
this.KeyBinding = wnd.ShortcutKey;
|
||||
}
|
||||
|
||||
|
|
40
NewUI/Controls/StateGrid.axaml
Normal file
40
NewUI/Controls/StateGrid.axaml
Normal file
|
@ -0,0 +1,40 @@
|
|||
<UserControl
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:Mesen.ViewModels"
|
||||
xmlns:c="using:Mesen.Controls"
|
||||
xmlns:cfg="using:Mesen.Config"
|
||||
xmlns:l="using:Mesen.Localization"
|
||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="400"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
Name="root"
|
||||
x:Class="Mesen.Controls.StateGrid"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<Color x:Key="ButtonBackgroundPointerOver">Transparent</Color>
|
||||
<Color x:Key="ButtonBackgroundPressed">Transparent</Color>
|
||||
</UserControl.Resources>
|
||||
|
||||
<DockPanel Margin="5" DataContext="{Binding ElementName=root}">
|
||||
<Panel DockPanel.Dock="Top">
|
||||
<TextBlock Text="{Binding Title}" FontSize="15" FontWeight="Medium" Foreground="White" TextAlignment="Center" />
|
||||
<Button IsVisible="{Binding ShowClose}" Click="OnCloseClick" HorizontalAlignment="Right" Background="Transparent" BorderThickness="0" Padding="0" Margin="0 -2 0 -2">
|
||||
<Image Source="/Assets/CloseWhite.png" Width="16" Height="16" />
|
||||
</Button>
|
||||
</Panel>
|
||||
<Button DockPanel.Dock="Left" IsVisible="{Binding ShowArrows}" Background="Transparent" BorderThickness="0" Click="OnPrevPageClick" VerticalAlignment="Stretch" Height="NaN">
|
||||
<Image Source="/Assets/MediaPlay.png" VerticalAlignment="Center" HorizontalAlignment="Left" Width="16">
|
||||
<Image.RenderTransform>
|
||||
<RotateTransform Angle="180" />
|
||||
</Image.RenderTransform>
|
||||
</Image>
|
||||
</Button>
|
||||
<Button DockPanel.Dock="Right" IsVisible="{Binding ShowArrows}" Background="Transparent" BorderThickness="0" Click="OnNextPageClick" VerticalAlignment="Stretch" Height="NaN">
|
||||
<Image Source="/Assets/MediaPlay.png" VerticalAlignment="Center" HorizontalAlignment="Right" Width="16" />
|
||||
</Button>
|
||||
<Grid Name="Grid" />
|
||||
</DockPanel>
|
||||
</UserControl>
|
185
NewUI/Controls/StateGrid.axaml.cs
Normal file
185
NewUI/Controls/StateGrid.axaml.cs
Normal file
|
@ -0,0 +1,185 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Mesen.ViewModels;
|
||||
using System;
|
||||
using Avalonia.Interactivity;
|
||||
using ReactiveUI;
|
||||
using System.Collections.Generic;
|
||||
using Mesen.Interop;
|
||||
|
||||
namespace Mesen.Controls
|
||||
{
|
||||
public class StateGrid : UserControl
|
||||
{
|
||||
public static readonly StyledProperty<List<RecentGameInfo>> EntriesProperty = AvaloniaProperty.Register<StateGrid, List<RecentGameInfo>>(nameof(Entries));
|
||||
|
||||
public static readonly StyledProperty<string> TitleProperty = AvaloniaProperty.Register<StateGrid, string>(nameof(Title));
|
||||
public static readonly StyledProperty<int> SelectedPageProperty = AvaloniaProperty.Register<StateGrid, int>(nameof(SelectedPage));
|
||||
public static readonly StyledProperty<bool> ShowArrowsProperty = AvaloniaProperty.Register<StateGrid, bool>(nameof(ShowArrows));
|
||||
public static readonly StyledProperty<bool> ShowCloseProperty = AvaloniaProperty.Register<StateGrid, bool>(nameof(ShowClose));
|
||||
public static readonly StyledProperty<GameScreenMode> ModeProperty = AvaloniaProperty.Register<StateGrid, GameScreenMode>(nameof(Mode));
|
||||
|
||||
public string Title
|
||||
{
|
||||
get { return GetValue(TitleProperty); }
|
||||
set { SetValue(TitleProperty, value); }
|
||||
}
|
||||
|
||||
public int SelectedPage
|
||||
{
|
||||
get { return GetValue(SelectedPageProperty); }
|
||||
set { SetValue(SelectedPageProperty, value); }
|
||||
}
|
||||
|
||||
public bool ShowArrows
|
||||
{
|
||||
get { return GetValue(ShowArrowsProperty); }
|
||||
set { SetValue(ShowArrowsProperty, value); }
|
||||
}
|
||||
|
||||
public bool ShowClose
|
||||
{
|
||||
get { return GetValue(ShowCloseProperty); }
|
||||
set { SetValue(ShowCloseProperty, value); }
|
||||
}
|
||||
|
||||
public GameScreenMode Mode
|
||||
{
|
||||
get { return GetValue(ModeProperty); }
|
||||
set { SetValue(ModeProperty, value); }
|
||||
}
|
||||
|
||||
public List<RecentGameInfo> Entries
|
||||
{
|
||||
get { return GetValue(EntriesProperty); }
|
||||
set { SetValue(EntriesProperty, value); }
|
||||
}
|
||||
|
||||
private int _colCount = 0;
|
||||
private int _rowCount = 0;
|
||||
|
||||
private int ElementsPerPage => _rowCount * _colCount;
|
||||
private int PageCount => (int)Math.Ceiling((double)Entries.Count / ElementsPerPage);
|
||||
|
||||
static StateGrid()
|
||||
{
|
||||
BoundsProperty.Changed.AddClassHandler<StateGrid>((x, e) => x.InitGrid());
|
||||
EntriesProperty.Changed.AddClassHandler<StateGrid>((x, e) => {
|
||||
x.SelectedPage = 0;
|
||||
x.InitGrid(true);
|
||||
});
|
||||
SelectedPageProperty.Changed.AddClassHandler<StateGrid>((x, e) => x.InitGrid(true));
|
||||
}
|
||||
|
||||
public StateGrid()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void OnCloseClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(DataContext is RecentGamesViewModel model) {
|
||||
if(model.NeedResume) {
|
||||
EmuApi.Resume();
|
||||
}
|
||||
model.Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPrevPageClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
int page = SelectedPage - 1;
|
||||
if(page < 0) {
|
||||
page = PageCount - 1;
|
||||
}
|
||||
SelectedPage = page;
|
||||
}
|
||||
|
||||
private void OnNextPageClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
int page = SelectedPage + 1;
|
||||
if(page >= PageCount) {
|
||||
page = 0;
|
||||
}
|
||||
SelectedPage = page;
|
||||
}
|
||||
|
||||
private void InitGrid(bool forceUpdate = false)
|
||||
{
|
||||
Grid grid = this.FindControl<Grid>("Grid");
|
||||
Size size = grid.Bounds.Size;
|
||||
|
||||
int colCount = Math.Min(4, Math.Max(1, (int)(size.Width / 300)));
|
||||
int rowCount = Math.Min(3, Math.Max(1, (int)(size.Height / 300)));
|
||||
|
||||
if(Entries.Count <= 1) {
|
||||
colCount = 1;
|
||||
rowCount = 1;
|
||||
} else if(Entries.Count <= 4) {
|
||||
colCount = Math.Min(2, colCount);
|
||||
rowCount = colCount;
|
||||
}
|
||||
|
||||
if(Mode != GameScreenMode.RecentGames) {
|
||||
colCount = 4;
|
||||
rowCount = 3;
|
||||
}
|
||||
|
||||
bool layoutChanged = _colCount != colCount || _rowCount != rowCount;
|
||||
if(!forceUpdate && !layoutChanged) {
|
||||
//Grid is already the same size
|
||||
return;
|
||||
}
|
||||
|
||||
if(layoutChanged) {
|
||||
SelectedPage = 0;
|
||||
}
|
||||
|
||||
_colCount = colCount;
|
||||
_rowCount = rowCount;
|
||||
|
||||
grid.Children.Clear();
|
||||
|
||||
grid.ColumnDefinitions = new ColumnDefinitions();
|
||||
for(int i = 0; i < colCount; i++) {
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition(1, GridUnitType.Star));
|
||||
}
|
||||
|
||||
grid.RowDefinitions = new RowDefinitions();
|
||||
for(int i = 0; i < rowCount; i++) {
|
||||
grid.RowDefinitions.Add(new RowDefinition(1, GridUnitType.Star));
|
||||
}
|
||||
|
||||
int elementsPerPage = ElementsPerPage;
|
||||
int startIndex = elementsPerPage * SelectedPage;
|
||||
|
||||
ShowArrows = Entries.Count > elementsPerPage;
|
||||
ShowClose = Mode != GameScreenMode.RecentGames;
|
||||
|
||||
for(int row = 0; row < rowCount; row++) {
|
||||
for(int col = 0; col < colCount; col++) {
|
||||
int index = startIndex + row * colCount + col;
|
||||
|
||||
if(index >= Entries.Count) {
|
||||
break;
|
||||
}
|
||||
|
||||
StateGridEntry ctrl = new StateGridEntry();
|
||||
|
||||
ctrl.SetValue(Grid.ColumnProperty, col);
|
||||
ctrl.SetValue(Grid.RowProperty, row);
|
||||
ctrl.Entry = Entries[index];
|
||||
ctrl.Init();
|
||||
|
||||
grid.Children.Add(ctrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
53
NewUI/Controls/StateGridEntry.axaml
Normal file
53
NewUI/Controls/StateGridEntry.axaml
Normal file
|
@ -0,0 +1,53 @@
|
|||
<UserControl
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="100" d:DesignHeight="100"
|
||||
xmlns:c="using:Mesen.Controls"
|
||||
x:Name="root"
|
||||
HorizontalAlignment="Stretch"
|
||||
x:Class="Mesen.Controls.StateGridEntry"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<Color x:Key="ButtonBorderBrushDisabled">Gray</Color>
|
||||
</UserControl.Resources>
|
||||
<UserControl.Styles>
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Style>
|
||||
<Style Selector="TextBlock:disabled">
|
||||
<Setter Property="Foreground" Value="Gray" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<DockPanel Margin="2 2 2 7" DataContext="{Binding ElementName=root}">
|
||||
<TextBlock
|
||||
DockPanel.Dock="Bottom"
|
||||
Text="{Binding SubTitle}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
IsEnabled="{Binding Enabled}"
|
||||
/>
|
||||
<TextBlock
|
||||
DockPanel.Dock="Bottom"
|
||||
Text="{Binding Title}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
IsEnabled="{Binding Enabled}"
|
||||
/>
|
||||
<Button
|
||||
BorderBrush="SlateGray"
|
||||
BorderThickness="2"
|
||||
Height="NaN"
|
||||
Background="Transparent"
|
||||
Click="OnImageClick"
|
||||
IsEnabled="{Binding Enabled}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Stretch"
|
||||
Padding="0"
|
||||
>
|
||||
<Image Source="{Binding Image}" Stretch="Uniform" StretchDirection="Both" VerticalAlignment="Center" />
|
||||
</Button>
|
||||
</DockPanel>
|
||||
</UserControl>
|
131
NewUI/Controls/StateGridEntry.axaml.cs
Normal file
131
NewUI/Controls/StateGridEntry.axaml.cs
Normal file
|
@ -0,0 +1,131 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Threading;
|
||||
using Mesen.Interop;
|
||||
using Mesen.Localization;
|
||||
using Mesen.ViewModels;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Mesen.Controls
|
||||
{
|
||||
public class StateGridEntry : UserControl
|
||||
{
|
||||
private static readonly WriteableBitmap EmptyImage = new WriteableBitmap(new PixelSize(256, 240), new Vector(96, 96), Avalonia.Platform.PixelFormat.Rgba8888, Avalonia.Platform.AlphaFormat.Opaque);
|
||||
|
||||
public static readonly StyledProperty<RecentGameInfo> EntryProperty = AvaloniaProperty.Register<StateGridEntry, RecentGameInfo>(nameof(Entry));
|
||||
public static readonly StyledProperty<Bitmap?> ImageProperty = AvaloniaProperty.Register<StateGridEntry, Bitmap?>(nameof(Image));
|
||||
public static readonly StyledProperty<string> TitleProperty = AvaloniaProperty.Register<StateGridEntry, string>(nameof(Title));
|
||||
public static readonly StyledProperty<string> SubTitleProperty = AvaloniaProperty.Register<StateGridEntry, string>(nameof(SubTitle));
|
||||
public static readonly StyledProperty<bool> EnabledProperty = AvaloniaProperty.Register<StateGridEntry, bool>(nameof(Enabled));
|
||||
|
||||
public RecentGameInfo Entry
|
||||
{
|
||||
get { return GetValue(EntryProperty); }
|
||||
set { SetValue(EntryProperty, value); }
|
||||
}
|
||||
|
||||
public Bitmap? Image
|
||||
{
|
||||
get { return GetValue(ImageProperty); }
|
||||
set { SetValue(ImageProperty, value); }
|
||||
}
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get { return GetValue(EnabledProperty); }
|
||||
set { SetValue(EnabledProperty, value); }
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
get { return GetValue(TitleProperty); }
|
||||
set { SetValue(TitleProperty, value); }
|
||||
}
|
||||
|
||||
public string SubTitle
|
||||
{
|
||||
get { return GetValue(SubTitleProperty); }
|
||||
set { SetValue(SubTitleProperty, value); }
|
||||
}
|
||||
|
||||
public StateGridEntry()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void OnImageClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
RecentGameInfo game = Entry;
|
||||
if(Path.GetExtension(game.FileName) == ".mss") {
|
||||
if(game.SaveMode) {
|
||||
EmuApi.SaveStateFile(game.FileName);
|
||||
} else {
|
||||
EmuApi.LoadStateFile(game.FileName);
|
||||
}
|
||||
EmuApi.Resume();
|
||||
} else {
|
||||
EmuApi.LoadRecentGame(Entry.FileName, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
RecentGameInfo game = Entry;
|
||||
if(game == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Title = game.Name;
|
||||
|
||||
bool fileExists = File.Exists(game.FileName);
|
||||
if(fileExists) {
|
||||
SubTitle = new FileInfo(game.FileName).LastWriteTime.ToString();
|
||||
} else {
|
||||
SubTitle = ResourceHelper.GetMessage("EmptyState");
|
||||
}
|
||||
Enabled = fileExists || game.SaveMode;
|
||||
|
||||
if(fileExists) {
|
||||
Task.Run(() => {
|
||||
Bitmap? img = null;
|
||||
try {
|
||||
if(Path.GetExtension(game.FileName) == ".mss") {
|
||||
img = EmuApi.GetSaveStatePreview(game.FileName);
|
||||
} else {
|
||||
using FileStream fs = File.Open(game.FileName, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
ZipArchive zip = new ZipArchive(fs);
|
||||
ZipArchiveEntry? entry = zip.GetEntry("Screenshot.png");
|
||||
if(entry != null) {
|
||||
using Stream stream = entry.Open();
|
||||
|
||||
//Copy to a memory stream (to avoid what looks like a Skia or Avalonia issue?)
|
||||
using MemoryStream ms = new MemoryStream();
|
||||
stream.CopyTo(ms);
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
img = new Bitmap(ms);
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
|
||||
Dispatcher.UIThread.Post(() => {
|
||||
Image = img;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
Image = StateGridEntry.EmptyImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
|||
using Mesen.Localization;
|
||||
using Mesen.Config.Shortcuts;
|
||||
using Mesen.Utilities;
|
||||
using Avalonia.Media.Imaging;
|
||||
|
||||
namespace Mesen.Interop
|
||||
{
|
||||
|
@ -94,8 +95,7 @@ namespace Mesen.Interop
|
|||
[DllImport(DllPath)] public static extern void LoadStateFile([MarshalAs(UnmanagedType.LPUTF8Str)]string filepath);
|
||||
|
||||
[DllImport(DllPath, EntryPoint = "GetSaveStatePreview")] private static extern Int32 GetSaveStatePreviewWrapper([MarshalAs(UnmanagedType.LPUTF8Str)]string saveStatePath, [Out]byte[] imgData);
|
||||
//TODO
|
||||
/*public static Image GetSaveStatePreview(string saveStatePath)
|
||||
public static Bitmap? GetSaveStatePreview(string saveStatePath)
|
||||
{
|
||||
if(File.Exists(saveStatePath)) {
|
||||
byte[] buffer = new byte[512*478*4];
|
||||
|
@ -103,12 +103,12 @@ namespace Mesen.Interop
|
|||
if(size > 0) {
|
||||
Array.Resize(ref buffer, size);
|
||||
using(MemoryStream stream = new MemoryStream(buffer)) {
|
||||
return Image.FromStream(stream);
|
||||
return new Bitmap(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}*/
|
||||
}
|
||||
|
||||
[DllImport(DllPath)] public static extern void SetCheats([In]UInt32[] cheats, UInt32 cheatCount);
|
||||
[DllImport(DllPath)] public static extern void ClearCheats();
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
<Control ID="mnuSaveState">Save State</Control>
|
||||
<Control ID="mnuLoadState">Load State</Control>
|
||||
<Control ID="mnuLoadLastSession">Load Last Session</Control>
|
||||
<Control ID="mnuLoadStateMenu">Load State Menu</Control>
|
||||
<Control ID="mnuSaveStateMenu">Save State Menu</Control>
|
||||
<Control ID="mnuRecentFiles">Recent Files</Control>
|
||||
<Control ID="mnuExit">Exit</Control>
|
||||
<Control ID="mnuGame">Game</Control>
|
||||
|
|
|
@ -199,6 +199,9 @@
|
|||
<Compile Update="Views\GameboyConfigView.axaml.cs">
|
||||
<DependentUpon>GameboyConfigView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Controls\StateGridEntry.axaml.cs">
|
||||
<DependentUpon>StateGridEntry.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Views\SnesConfigView.axaml.cs">
|
||||
<DependentUpon>SnesConfigView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
|
@ -214,6 +217,9 @@
|
|||
<Compile Update="Views\NesControllerView.axaml.cs">
|
||||
<DependentUpon>NesControllerView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Controls\StateGrid.axaml.cs">
|
||||
<DependentUpon>StateGrid.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Windows\ColorPickerWindow.axaml.cs">
|
||||
<DependentUpon>ColorPickerWindow.axaml</DependentUpon>
|
||||
</Compile>
|
||||
|
|
|
@ -10,13 +10,24 @@ namespace Mesen.Utilities
|
|||
{
|
||||
static class WindowExtensions
|
||||
{
|
||||
public static void ShowCentered(this Window child, Window parent)
|
||||
private static void CenterWindow(Window child, Window parent)
|
||||
{
|
||||
child.WindowStartupLocation = WindowStartupLocation.Manual;
|
||||
Size wndCenter = (parent.ClientSize / 2);
|
||||
PixelPoint screenCenter = new PixelPoint(parent.Position.X + (int)wndCenter.Width, parent.Position.Y + (int)wndCenter.Height);
|
||||
child.Position = new PixelPoint(screenCenter.X - (int)child.Width / 2, screenCenter.Y - (int)child.Height / 2);
|
||||
}
|
||||
|
||||
public static void ShowCentered(this Window child, Window parent)
|
||||
{
|
||||
CenterWindow(child, parent);
|
||||
child.Show();
|
||||
}
|
||||
|
||||
public static Task ShowCenteredDialog(this Window child, Window parent)
|
||||
{
|
||||
CenterWindow(child, parent);
|
||||
return child.ShowDialog(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,17 +27,22 @@ namespace Mesen.ViewModels
|
|||
[Reactive] public bool HasRecentItems { get; private set; }
|
||||
public ReactiveCommand<RecentItem, Unit> OpenRecentCommand { get; }
|
||||
|
||||
[Reactive] public RecentGamesViewModel RecentGames { get; private set; }
|
||||
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
OpenRecentCommand = ReactiveCommand.Create<RecentItem>(OpenRecent);
|
||||
|
||||
RomInfo = new RomInfo();
|
||||
|
||||
RecentItems = ConfigManager.Config.RecentFiles.Items;
|
||||
|
||||
this.WhenAnyValue(x => x.RecentItems.Count).Subscribe(count => {
|
||||
HasRecentItems = count > 0;
|
||||
});
|
||||
|
||||
RecentGames = new RecentGamesViewModel();
|
||||
RecentGames.Init(GameScreenMode.RecentGames);
|
||||
|
||||
this.WhenAnyValue(x => x.RomInfo).Subscribe(x => {
|
||||
IsGameRunning = x.Format != RomFormat.Unknown;
|
||||
|
106
NewUI/ViewModels/RecentGamesViewModel.cs
Normal file
106
NewUI/ViewModels/RecentGamesViewModel.cs
Normal file
|
@ -0,0 +1,106 @@
|
|||
using Mesen.Config;
|
||||
using Mesen.Interop;
|
||||
using Mesen.Localization;
|
||||
using Mesen.Utilities;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Mesen.ViewModels
|
||||
{
|
||||
public class RecentGamesViewModel : ViewModelBase
|
||||
{
|
||||
[Reactive] public bool Visible { get; set; }
|
||||
[Reactive] public bool NeedResume { get; private set; }
|
||||
[Reactive] public string Title { get; private set; } = "";
|
||||
[Reactive] public GameScreenMode Mode { get; private set; }
|
||||
[Reactive] public List<RecentGameInfo> GameEntries { get; private set; } = new List<RecentGameInfo>();
|
||||
|
||||
public RecentGamesViewModel()
|
||||
{
|
||||
}
|
||||
|
||||
public void Init(GameScreenMode mode)
|
||||
{
|
||||
if(mode == GameScreenMode.RecentGames && ConfigManager.Config.Preferences.DisableGameSelectionScreen) {
|
||||
Visible = false;
|
||||
GameEntries = new List<RecentGameInfo>();
|
||||
return;
|
||||
} else if(mode != GameScreenMode.RecentGames && Mode == mode && Visible) {
|
||||
Visible = false;
|
||||
if(NeedResume) {
|
||||
EmuApi.Resume();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Mode = mode;
|
||||
|
||||
List<RecentGameInfo> entries = new();
|
||||
|
||||
if(mode == GameScreenMode.RecentGames) {
|
||||
NeedResume = false;
|
||||
Title = string.Empty;
|
||||
|
||||
List<string> files = Directory.GetFiles(ConfigManager.RecentGamesFolder, "*.rgd").OrderByDescending((file) => new FileInfo(file).LastWriteTime).ToList();
|
||||
for(int i = 0; i < files.Count && entries.Count < 72; i++) {
|
||||
entries.Add(new RecentGameInfo() { FileName = files[i], Name = Path.GetFileNameWithoutExtension(files[i]) });
|
||||
}
|
||||
} else {
|
||||
if(!Visible) {
|
||||
NeedResume = Pause();
|
||||
}
|
||||
|
||||
Title = mode == GameScreenMode.LoadState ? ResourceHelper.GetMessage("LoadStateDialog") : ResourceHelper.GetMessage("SaveStateDialog");
|
||||
|
||||
string romName = EmuApi.GetRomInfo().GetRomName();
|
||||
for(int i = 0; i < (mode == GameScreenMode.LoadState ? 11 : 10); i++) {
|
||||
entries.Add(new RecentGameInfo() {
|
||||
FileName = Path.Combine(ConfigManager.SaveStateFolder, romName + "_" + (i + 1) + ".mss"),
|
||||
Name = i == 10 ? ResourceHelper.GetMessage("AutoSave") : ResourceHelper.GetMessage("SlotNumber", i + 1),
|
||||
SaveMode = mode == GameScreenMode.SaveState
|
||||
});
|
||||
}
|
||||
if(mode == GameScreenMode.LoadState) {
|
||||
entries.Add(new RecentGameInfo() {
|
||||
FileName = Path.Combine(ConfigManager.RecentGamesFolder, romName + ".rgd"),
|
||||
Name = ResourceHelper.GetMessage("LastSession")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Visible = entries.Count > 0;
|
||||
GameEntries = entries;
|
||||
}
|
||||
|
||||
private bool Pause()
|
||||
{
|
||||
if(!EmuApi.IsPaused()) {
|
||||
EmuApi.Pause();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public enum GameScreenMode
|
||||
{
|
||||
RecentGames,
|
||||
LoadState,
|
||||
SaveState
|
||||
}
|
||||
|
||||
public class RecentGameInfo
|
||||
{
|
||||
public string FileName { get; set; }
|
||||
public string Name { get; set; }
|
||||
public bool SaveMode { get; set; }
|
||||
}
|
||||
}
|
|
@ -27,17 +27,25 @@
|
|||
<Style Selector="Slider TickBar">
|
||||
<Setter Property="Fill" Value="Gray" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.Toggle">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
</Style>
|
||||
<Style Selector="Button.Toggle:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Panel Background="Black">
|
||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<Button Click="OnToggleShuffleClick" Height="26">
|
||||
<Button Click="OnToggleShuffleClick" Height="26" Classes="Toggle">
|
||||
<Panel>
|
||||
<Image Height="24" Width="24" Source="/Assets/ShuffleEnabled.png" IsVisible="{CompiledBinding Config.Shuffle}" />
|
||||
<Image Height="24" Width="24" Source="/Assets/Shuffle.png" IsVisible="{CompiledBinding !Config.Shuffle}" />
|
||||
</Panel>
|
||||
</Button>
|
||||
<Button Click="OnToggleRepeatClick" Height="26">
|
||||
<Button Click="OnToggleRepeatClick" Height="26" Classes="Toggle">
|
||||
<Panel>
|
||||
<Image Height="24" Width="24" Source="/Assets/RepeatEnabled.png" IsVisible="{CompiledBinding Config.Repeat}" />
|
||||
<Image Height="24" Width="24" Source="/Assets/Repeat.png" IsVisible="{CompiledBinding !Config.Repeat}" />
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
TextWrapping="Wrap"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
/>
|
||||
/>
|
||||
</Panel>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
|
|
|
@ -34,14 +34,14 @@ namespace Mesen.Windows
|
|||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
protected void OnPreviewKeyDown(object? sender, KeyEventArgs e)
|
||||
private void OnPreviewKeyDown(object? sender, KeyEventArgs e)
|
||||
{
|
||||
InputApi.SetKeyState((int)e.Key, true);
|
||||
this.OnKeyChange();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected void OnPreviewKeyUp(object? sender, KeyEventArgs e)
|
||||
private void OnPreviewKeyUp(object? sender, KeyEventArgs e)
|
||||
{
|
||||
InputApi.SetKeyState((int)e.Key, false);
|
||||
this.OnKeyChange();
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:m="clr-namespace:Mesen"
|
||||
xmlns:c="using:Mesen.Controls"
|
||||
xmlns:v="using:Mesen.Views"
|
||||
xmlns:l="using:Mesen.Localization"
|
||||
xmlns:vm="using:Mesen.ViewModels"
|
||||
|
@ -23,6 +24,9 @@
|
|||
<DataTemplate DataType="{x:Type vm:AudioPlayerViewModel}">
|
||||
<v:AudioPlayerView />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type vm:RecentGamesViewModel}">
|
||||
<c:StateGrid Entries="{Binding GameEntries}" Title="{Binding Title}" Mode="{Binding Mode}" />
|
||||
</DataTemplate>
|
||||
</Window.DataTemplates>
|
||||
|
||||
<DockPanel Background="Black">
|
||||
|
@ -32,11 +36,11 @@
|
|||
<MenuItem.Icon><Image Source="/Assets/Folder.png" /></MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator/>
|
||||
<MenuItem Header="{l:Translate mnuSaveState}" Click="OnExitClick">
|
||||
|
||||
<MenuItem Header="{l:Translate mnuSaveState}">
|
||||
<MenuItem Header="{l:Translate mnuSaveStateMenu}" Click="OnSaveStateMenuClick" IsEnabled="{CompiledBinding IsGameRunning}" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="{l:Translate mnuLoadState}" Click="OnExitClick">
|
||||
|
||||
<MenuItem Header="{l:Translate mnuLoadState}">
|
||||
<MenuItem Header="{l:Translate mnuLoadStateMenu}" Click="OnLoadStateMenuClick" IsEnabled="{CompiledBinding IsGameRunning}" />
|
||||
</MenuItem>
|
||||
<Separator/>
|
||||
<MenuItem Header="{l:Translate mnuRecentFiles}" Items="{CompiledBinding RecentItems}" Classes="RecentFiles" IsEnabled="{CompiledBinding HasRecentItems}">
|
||||
|
@ -237,7 +241,12 @@
|
|||
</MenuItem>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
<ContentControl DockPanel.Dock="Bottom" Content="{CompiledBinding AudioPlayer}" />
|
||||
<m:NativeRenderer Name="Renderer" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
|
||||
<Panel>
|
||||
<ContentControl Content="{CompiledBinding RecentGames}" IsVisible="{CompiledBinding RecentGames.Visible}" />
|
||||
<m:NativeRenderer Name="Renderer" HorizontalAlignment="Center" VerticalAlignment="Center" IsVisible="{CompiledBinding !RecentGames.Visible}" />
|
||||
</Panel>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
|
|
|
@ -26,14 +26,21 @@ namespace Mesen.Windows
|
|||
{
|
||||
public class MainWindow : Window
|
||||
{
|
||||
private NotificationListener? _listener;
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
private MainWindowViewModel _model = null;
|
||||
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
|
||||
private NotificationListener? _listener = null;
|
||||
private ConfigWindow? _cfgWindow = null;
|
||||
private MainWindowViewModel? _model = null;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
AddHandler(DragDrop.DropEvent, OnDrop);
|
||||
|
||||
//Allow us to catch LeftAlt/RightAlt key presses
|
||||
AddHandler(InputElement.KeyDownEvent, OnPreviewKeyDown, RoutingStrategies.Tunnel, true);
|
||||
AddHandler(InputElement.KeyUpEvent, OnPreviewKeyUp, RoutingStrategies.Tunnel, true);
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
|
@ -88,10 +95,10 @@ namespace Mesen.Windows
|
|||
|
||||
//ConfigApi.SetEmulationFlag(EmulationFlags.MaximumSpeed, true);
|
||||
|
||||
if(!ProcessCommandLineArgs(Program.CommandLineArgs)) {
|
||||
/*if(!ProcessCommandLineArgs(Program.CommandLineArgs)) {
|
||||
EmuApi.LoadRom(@"C:\Code\Games\Super Mario Bros. (USA).nes");
|
||||
//EmuApi.LoadRom(@"C:\Code\Mesen-S\PGOHelper\PGOGames\Super Mario World (USA).sfc");
|
||||
}
|
||||
}*/
|
||||
|
||||
ConfigManager.Config.Preferences.UpdateFileAssociations();
|
||||
|
||||
|
@ -120,13 +127,21 @@ namespace Mesen.Windows
|
|||
case ConsoleNotificationType.GameLoaded:
|
||||
RomInfo romInfo = EmuApi.GetRomInfo();
|
||||
Dispatcher.UIThread.Post(() => {
|
||||
_model!.RomInfo = romInfo;
|
||||
_model.RecentGames.Visible = false;
|
||||
_model.RomInfo = romInfo;
|
||||
});
|
||||
break;
|
||||
|
||||
case ConsoleNotificationType.GameResumed:
|
||||
Dispatcher.UIThread.Post(() => {
|
||||
_model.RecentGames.Visible = false;
|
||||
});
|
||||
break;
|
||||
|
||||
case ConsoleNotificationType.EmulationStopped:
|
||||
Dispatcher.UIThread.Post(() => {
|
||||
_model!.RomInfo = new RomInfo();
|
||||
_model.RomInfo = new RomInfo();
|
||||
_model.RecentGames.Init(GameScreenMode.RecentGames);
|
||||
});
|
||||
break;
|
||||
|
||||
|
@ -164,6 +179,16 @@ namespace Mesen.Windows
|
|||
LoadRomHelper.LoadFile(filenames[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSaveStateMenuClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_model.RecentGames.Init(GameScreenMode.SaveState);
|
||||
}
|
||||
|
||||
private void OnLoadStateMenuClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_model.RecentGames.Init(GameScreenMode.LoadState);
|
||||
}
|
||||
|
||||
private void OnTileViewerClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
|
@ -201,6 +226,11 @@ namespace Mesen.Windows
|
|||
private void cfgWindow_Closed(object? sender, EventArgs e)
|
||||
{
|
||||
_cfgWindow = null;
|
||||
if(ConfigManager.Config.Preferences.DisableGameSelectionScreen && _model.RecentGames.Visible) {
|
||||
_model.RecentGames.Visible = false;
|
||||
} else if(!ConfigManager.Config.Preferences.DisableGameSelectionScreen && !_model.IsGameRunning) {
|
||||
_model.RecentGames.Init(GameScreenMode.RecentGames);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPreferencesClick(object sender, RoutedEventArgs e)
|
||||
|
@ -332,16 +362,22 @@ namespace Mesen.Windows
|
|||
EmuApi.ExecuteShortcut(new ExecuteShortcutParams() { Shortcut = EmulatorShortcut.FdsInsertDiskNumber, Param = 1 });
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
private void OnPreviewKeyDown(object? sender, KeyEventArgs e)
|
||||
{
|
||||
base.OnKeyDown(e);
|
||||
InputApi.SetKeyState((int)e.Key, true);
|
||||
if(e.Key == Key.Tab) {
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnKeyUp(KeyEventArgs e)
|
||||
private void OnPreviewKeyUp(object? sender, KeyEventArgs e)
|
||||
{
|
||||
base.OnKeyUp(e);
|
||||
InputApi.SetKeyState((int)e.Key, false);
|
||||
}
|
||||
|
||||
protected override void OnLostFocus(RoutedEventArgs e)
|
||||
{
|
||||
InputApi.ResetKeyState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue