recording: Many adjustments on VirtualPad from review feedback

recording: Factor in DPIScaling when scaling VirtualPad window


recording: Add a reset button, hopefully better quality backgrounds for lower resolutions


recording: Improved VPad analog alignment

Co-authored-by: sonicfind
recording: Some final VirtualPad coordinate adjustments
This commit is contained in:
Tyler Wilding 2020-09-27 20:18:19 -04:00 committed by refractionpcsx2
parent 28a4d0390f
commit 400ed82715
18 changed files with 266 additions and 221 deletions

3
.gitattributes vendored
View File

@ -6,6 +6,3 @@
*.props text eol=crlf
*.vcxproj text eol=crlf
*.vcxproj.filters text eol=crlf
# Hide generated files for easier pull-request reviewing
pcsx2/Recording/VirtualPad/img/*.h linguist-generated

View File

@ -78,13 +78,13 @@ jobs:
string(TIMESTAMP current_date "%Y-%m-%d-%H;%M;%S" UTC)
message("::set-output name=timestamp::${current_date}")
# - name: ccache cache files
# uses: actions/cache@v2
# with:
# path: .ccache
# key: ${{ matrix.os }}-${{ matrix.platform }}-${{ matrix.compiler }}-${{ matrix.compiler-version }}-ccache-${{ steps.ccache_cache_timestamp.outputs.timestamp }}
# restore-keys: |
# ${{ matrix.os }}-${{ matrix.platform }}-${{ matrix.compiler }}-${{ matrix.compiler-version }}-ccache-
- name: ccache cache files
uses: actions/cache@v2
with:
path: .ccache
key: ${{ matrix.os }}-${{ matrix.platform }}-${{ matrix.compiler }}-${{ matrix.compiler-version }}-ccache-${{ steps.ccache_cache_timestamp.outputs.timestamp }}
restore-keys: |
${{ matrix.os }}-${{ matrix.platform }}-${{ matrix.compiler }}-${{ matrix.compiler-version }}-ccache-
- name: Install Packages
env:
@ -102,4 +102,3 @@ jobs:
chmod +x .github/workflows/scripts/build-linux.sh
./.github/workflows/scripts/build-linux.sh generate_cmake
./.github/workflows/scripts/build-linux.sh compile
ls ./pcsx2/Recording/VirtualPad/img

View File

@ -507,7 +507,9 @@ set(pcsx2RecordingHeaders
set(res_rec_vp_src "${CMAKE_SOURCE_DIR}/pcsx2/Recording/VirtualPad/img")
set(pcsx2RecordingVirtualPadResources
${res_rec_vp_src}/circlePressed.h
${res_rec_vp_src}/controller.h
${res_rec_vp_src}/controllerFull.h
${res_rec_vp_src}/controllerHalf.h
${res_rec_vp_src}/controllerThreeQuarters.h
${res_rec_vp_src}/crossPressed.h
${res_rec_vp_src}/downPressed.h
${res_rec_vp_src}/l1Pressed.h
@ -760,7 +762,7 @@ endforeach()
### Generate Recording resource files
### Drop them into the folder alongside the png files
foreach(res_file IN ITEMS
circlePressed controller crossPressed downPressed l1Pressed l2Pressed l3Pressed leftPressed
circlePressed controllerFull controllerHalf controllerThreeQuarters crossPressed downPressed l1Pressed l2Pressed l3Pressed leftPressed
r1Pressed r2Pressed r3Pressed rightPressed selectPressed squarePressed startPressed trianglePressed upPressed)
add_custom_command(OUTPUT "${res_rec_vp_src}/${res_file}.h" COMMAND perl ${CMAKE_SOURCE_DIR}/linux_various/hex2h.pl "${res_rec_vp_src}/${res_file}.png" "${res_rec_vp_src}/${res_file}" )
endforeach()

View File

@ -64,7 +64,7 @@ InputRecording::InputRecording()
padData[CONTROLLER_PORT_TWO] = new PadData();
}
void InputRecording::setVirtualPadPtr(VirtualPad *ptr, int const port)
void InputRecording::setVirtualPadPtr(VirtualPad* ptr, int const port)
{
virtualPads[port] = ptr;
}
@ -85,21 +85,19 @@ void InputRecording::RecordingReset()
g_InputRecordingControls.Resume();
}
void InputRecording::ControllerInterrupt(u8 &data, u8 &port, u16 &bufCount, u8 buf[])
void InputRecording::ControllerInterrupt(u8& data, u8& port, u16& bufCount, u8 buf[])
{
// TODO - Multi-Tap Support
if (bufCount == 1)
fInterruptFrame = data == READ_DATA_AND_VIBRATE_FIRST_BYTE;
else if (bufCount == 2)
{
if (buf[bufCount] != READ_DATA_AND_VIBRATE_SECOND_BYTE)
else if (bufCount == 2 && buf[bufCount] != READ_DATA_AND_VIBRATE_SECOND_BYTE)
fInterruptFrame = false;
}
// We do not want to record or save the first two bytes in the data returned from the PAD plugin
else if (fInterruptFrame && bufCount >= 3 && frameCounter >= 0 && frameCounter < INT_MAX)
{
u8 &bufVal = buf[bufCount];
u8& bufVal = buf[bufCount];
const u16 bufIndex = bufCount - 3;
if (state == InputRecordingMode::Replaying)
@ -109,39 +107,32 @@ void InputRecording::ControllerInterrupt(u8 &data, u8 &port, u16 &bufCount, u8 b
{
// Overwrite value originally provided by the PAD plugin
bufVal = tmp;
// Update controller data state for future VirtualPad / logging usage.
padData[port]->UpdateControllerData(bufIndex, bufVal);
if (virtualPads[port] && virtualPads[port]->IsShown())
{
virtualPads[port]->UpdateControllerData(bufIndex, padData[port]);
}
}
}
// Update controller data state for future VirtualPad / logging usage.
padData[port]->UpdateControllerData(bufIndex, bufVal);
if (virtualPads[port] && virtualPads[port]->IsShown())
if (virtualPads[port] &&
virtualPads[port]->IsShown() &&
virtualPads[port]->UpdateControllerData(bufIndex, padData[port]) &&
state != InputRecordingMode::Replaying)
{
// If the VirtualPad updated the PadData, we have to update the buffer
// before committing it to the recording / sending it to the game
// - Do not do this if we are in replay mode!
if (virtualPads[port]->UpdateControllerData(bufIndex, padData[port]) && state != InputRecordingMode::Replaying)
{
bufVal = padData[port]->PollControllerData(bufIndex);
}
}
// If we have reached the end of the pad data, log it out
if (bufIndex == PadData::END_INDEX_CONTROLLER_BUFFER) {
if (bufIndex == PadData::END_INDEX_CONTROLLER_BUFFER)
{
padData[port]->LogPadData(port);
// As well as re-render the virtual pad UI, if applicable
// - Don't render if it's minimized
if (virtualPads[port] && virtualPads[port]->IsShown() && !virtualPads[port]->IsIconized())
{
virtualPads[port]->Redraw();
}
}
// Finally, commit the byte to the movie file if we are recording
if (state == InputRecordingMode::Recording)
@ -161,7 +152,7 @@ s32 InputRecording::GetFrameCounter()
return frameCounter;
}
InputRecordingFile &InputRecording::GetInputRecordingData()
InputRecordingFile& InputRecording::GetInputRecordingData()
{
return inputRecordingData;
}
@ -223,12 +214,16 @@ wxString InputRecording::RecordingModeTitleSegment()
void InputRecording::SetToRecordMode()
{
state = InputRecordingMode::Recording;
virtualPads[CONTROLLER_PORT_ONE]->SetReadOnlyMode(false);
virtualPads[CONTROLLER_PORT_TWO]->SetReadOnlyMode(false);
recordingConLog("[REC]: Record mode ON.\n");
}
void InputRecording::SetToReplayMode()
{
state = InputRecordingMode::Replaying;
virtualPads[CONTROLLER_PORT_ONE]->SetReadOnlyMode(true);
virtualPads[CONTROLLER_PORT_TWO]->SetReadOnlyMode(true);
recordingConLog("[REC]: Replay mode ON.\n");
}
@ -272,6 +267,8 @@ void InputRecording::SetStartingFrame(u32 newStartingFrame)
void InputRecording::Stop()
{
state = InputRecordingMode::NotActive;
virtualPads[CONTROLLER_PORT_ONE]->SetReadOnlyMode(false);
virtualPads[CONTROLLER_PORT_TWO]->SetReadOnlyMode(false);
incrementUndo = false;
if (inputRecordingData.Close())
recordingConLog(L"[REC]: InputRecording Recording Stopped.\n");
@ -303,7 +300,7 @@ bool InputRecording::Create(wxString FileName, bool fromSaveState, wxString auth
inputRecordingData.GetHeader().SetGameName(resolveGameName());
// Write header contents
inputRecordingData.WriteHeader();
state = InputRecordingMode::Recording;
SetToRecordMode();
g_InputRecordingControls.DisableFrameAdvance();
recordingConLog(wxString::Format(L"[REC]: Started new recording - [%s]\n", FileName));
return true;
@ -348,7 +345,7 @@ bool InputRecording::Play(wxString fileName)
recordingConLog(L"[REC]: Recording was possibly constructed for a different game.\n");
incrementUndo = true;
state = InputRecordingMode::Replaying;
SetToReplayMode();
g_InputRecordingControls.DisableFrameAdvance();
recordingConLog(wxString::Format(L"[REC]: Replaying input recording - [%s]\n", inputRecordingData.GetFilename()));
recordingConLog(wxString::Format(L"[REC]: PCSX2 Version Used: %s\n", inputRecordingData.GetHeader().emu));

View File

@ -34,12 +34,12 @@ public:
// Main handler for ingesting input data and either saving it to the recording file (recording)
// or mutating it to the contents of the recording file (replaying)
void ControllerInterrupt(u8 &data, u8 &port, u16 &BufCount, u8 buf[]);
void ControllerInterrupt(u8& data, u8& port, u16& BufCount, u8 buf[]);
// The running frame counter for the input recording
s32 GetFrameCounter();
InputRecordingFile &GetInputRecordingData();
InputRecordingFile& GetInputRecordingData();
// The internal PCSX2 g_FrameCount value on the first frame of the recording
u32 GetStartingFrame();
@ -87,8 +87,8 @@ public:
bool Play(wxString filename);
// Stop the active input recording
void Stop();
void setVirtualPadPtr(VirtualPad *ptr, int const port);
// Initialze VirtualPad window
void setVirtualPadPtr(VirtualPad* ptr, int const port);
private:
enum class InputRecordingMode
@ -118,10 +118,10 @@ private:
InputRecordingMode state = InputRecording::InputRecordingMode::NotActive;
// Controller Data
PadData *padData[2];
PadData* padData[2];
// VirtualPads
VirtualPad *virtualPads[2];
VirtualPad* virtualPads[2];
// Resolve the name and region of the game currently loaded using the GameDB
// If the game cannot be found in the DB, the fallback is the ISO filename

View File

@ -19,13 +19,13 @@
#ifndef DISABLE_RECORDING
NewRecordingFrame::NewRecordingFrame(wxWindow *parent)
NewRecordingFrame::NewRecordingFrame(wxWindow* parent)
: wxDialog(parent, wxID_ANY, "New Input Recording", wxDefaultPosition, wxDefaultSize, wxSTAY_ON_TOP | wxCAPTION)
{
wxPanel *panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _("panel"));
wxPanel* panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _("panel"));
wxFlexGridSizer *fgs = new wxFlexGridSizer(4, 2, 20, 20);
wxBoxSizer *container = new wxBoxSizer(wxVERTICAL);
wxFlexGridSizer* fgs = new wxFlexGridSizer(4, 2, 20, 20);
wxBoxSizer* container = new wxBoxSizer(wxVERTICAL);
m_fileLabel = new wxStaticText(panel, wxID_ANY, _("File Path"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER);
m_authorLabel = new wxStaticText(panel, wxID_ANY, _("Author"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER);
@ -65,7 +65,8 @@ wxString NewRecordingFrame::GetFile() const
wxString path = m_filePicker->GetPath();
// wxWidget's removes the extension if it contains wildcards
// on wxGTK https://trac.wxwidgets.org/ticket/15285
if (!path.EndsWith(".p2m2")) {
if (!path.EndsWith(".p2m2"))
{
return wxString::Format("%s.p2m2", path);
}
return path;

View File

@ -31,20 +31,20 @@ enum MenuIds_New_Recording_Frame
class NewRecordingFrame : public wxDialog
{
public:
NewRecordingFrame(wxWindow *parent);
NewRecordingFrame(wxWindow* parent);
wxString GetFile() const;
wxString GetAuthor() const;
int GetFrom() const;
private:
wxStaticText *m_fileLabel;
wxFilePickerCtrl *m_filePicker;
wxStaticText *m_authorLabel;
wxTextCtrl *m_authorInput;
wxStaticText *m_fromLabel;
wxChoice *m_fromChoice;
wxButton *m_startRecording;
wxButton *m_cancelRecording;
wxStaticText* m_fileLabel;
wxFilePickerCtrl* m_filePicker;
wxStaticText* m_authorLabel;
wxTextCtrl* m_authorInput;
wxStaticText* m_fromLabel;
wxChoice* m_fromChoice;
wxButton* m_startRecording;
wxButton* m_cancelRecording;
};
#endif

View File

@ -184,10 +184,8 @@ wxString PadData::RawPadBytesToString(int start, int end)
{
str += wxString::Format("%d", PollControllerData(i));
if (i != end - 1)
{
str += ", ";
}
}
return str;
}

View File

@ -20,6 +20,7 @@
#include <math.h>
#include "App.h"
#include "MSWstuff.h"
#include "Utilities/EmbeddedImage.h"
#include "wx/dcbuffer.h"
#include "wx/display.h"
@ -29,7 +30,9 @@
#include "Recording/VirtualPad/VirtualPadResources.h"
#include "Recording/VirtualPad/img/circlePressed.h"
#include "Recording/VirtualPad/img/controller.h"
#include "Recording/VirtualPad/img/controllerFull.h"
#include "Recording/VirtualPad/img/controllerThreeQuarters.h"
#include "Recording/VirtualPad/img/controllerHalf.h"
#include "Recording/VirtualPad/img/crossPressed.h"
#include "Recording/VirtualPad/img/downPressed.h"
#include "Recording/VirtualPad/img/l1Pressed.h"
@ -46,57 +49,72 @@
#include "Recording/VirtualPad/img/trianglePressed.h"
#include "Recording/VirtualPad/img/upPressed.h"
VirtualPad::VirtualPad(wxWindow* parent, int controllerPort, AppConfig::InputRecordingOptions& options)
: wxFrame(parent, wxID_ANY, wxEmptyString)
, options(options)
{
// Images at 1.00 scale are designed to work well on HiDPI (4k) at 150% scaling (default recommended setting on windows)
// Therefore, on a 1080p monitor we halve the scaling, on 1440p we reduce it by 25%, which from some quick tests looks comparable
// Side-note - It would be possible to factor in monitor scaling, but considering that is platform specific (with some platforms only supporting
// integer scaling) this is likely not reliable.
// Side-note - Getting the DPI scaling amount is platform specific (with some platforms only supporting
// integer scaling as well) this is likely not reliable.
// Slight multi-monitor support, will use whatever window pcsx2 is opened with, but won't currently re-init if
// windows are dragged between differing monitors!
wxDisplay display(wxDisplay::GetFromWindow(this));
const wxRect screen = display.GetClientArea();
float dpiScale = MSW_GetDPIScale(); // linux returns 1.0
if (screen.height > 1080 && screen.height <= 1440) // 1440p display
{
scalingFactor = 0.75;
}
scalingFactor = 0.75 * dpiScale;
else if (screen.height <= 1080) // 1080p display
{
scalingFactor = 0.5;
} // otherwise use default 1.0 scaling
scalingFactor = 0.5 * dpiScale;
}
// otherwise use default 1.0 scaling
virtualPadData = VirtualPadData();
virtualPadData.background = NewBitmap(EmbeddedImage<res_controller>().Get(), wxPoint(0, 0));
// Based on the scaling factor, select the appropriate background image
// Don't scale these images as they've already been pre-scaled
if (floatCompare(scalingFactor, 0.5))
virtualPadData.background = NewBitmap(EmbeddedImage<res_controllerHalf>().Get(), wxPoint(0, 0), true);
else if (floatCompare(scalingFactor, 0.75))
virtualPadData.background = NewBitmap(EmbeddedImage<res_controllerThreeQuarters>().Get(), wxPoint(0, 0), true);
else
// Otherwise, scale down/up (or don't in the case of 1.0) the largst image
virtualPadData.background = NewBitmap(EmbeddedImage<res_controllerFull>().Get(), wxPoint(0, 0));
// Use the background image's size to define the window size
SetClientSize(virtualPadData.background.width, virtualPadData.background.height);
InitPressureButtonGuiElements(virtualPadData.cross, NewBitmap(EmbeddedImage<res_crossPressed>().Get(), wxPoint(938, 369)), this, wxPoint(1055, 525));
InitPressureButtonGuiElements(virtualPadData.circle, NewBitmap(EmbeddedImage<res_circlePressed>().Get(), wxPoint(1024, 286)), this, wxPoint(1055, 565));
InitPressureButtonGuiElements(virtualPadData.triangle, NewBitmap(EmbeddedImage<res_trianglePressed>().Get(), wxPoint(938, 201)), this, wxPoint(1055, 605));
InitPressureButtonGuiElements(virtualPadData.square, NewBitmap(EmbeddedImage<res_squarePressed>().Get(), wxPoint(852, 287)), this, wxPoint(1055, 645));
// These hard-coded pixels correspond to where the background image's components are (ie. the buttons)
// Everything is automatically scaled and adjusted based on the `scalingFactor` variable
InitPressureButtonGuiElements(virtualPadData.square, NewBitmap(EmbeddedImage<res_squarePressed>().Get(), wxPoint(852, 287)), this, wxPoint(1055, 525));
InitPressureButtonGuiElements(virtualPadData.triangle, NewBitmap(EmbeddedImage<res_trianglePressed>().Get(), wxPoint(938, 201)), this, wxPoint(1055, 565));
InitPressureButtonGuiElements(virtualPadData.circle, NewBitmap(EmbeddedImage<res_circlePressed>().Get(), wxPoint(1024, 286)), this, wxPoint(1055, 605));
InitPressureButtonGuiElements(virtualPadData.cross, NewBitmap(EmbeddedImage<res_crossPressed>().Get(), wxPoint(938, 369)), this, wxPoint(1055, 645));
InitPressureButtonGuiElements(virtualPadData.down, NewBitmap(EmbeddedImage<res_downPressed>().Get(), wxPoint(186, 359)), this, wxPoint(175, 525), true);
InitPressureButtonGuiElements(virtualPadData.right, NewBitmap(EmbeddedImage<res_rightPressed>().Get(), wxPoint(248, 302)), this, wxPoint(175, 565), true);
InitPressureButtonGuiElements(virtualPadData.up, NewBitmap(EmbeddedImage<res_upPressed>().Get(), wxPoint(186, 227)), this, wxPoint(175, 605), true);
InitPressureButtonGuiElements(virtualPadData.left, NewBitmap(EmbeddedImage<res_leftPressed>().Get(), wxPoint(110, 302)), this, wxPoint(175, 645), true);
InitPressureButtonGuiElements(virtualPadData.left, NewBitmap(EmbeddedImage<res_leftPressed>().Get(), wxPoint(110, 303)), this, wxPoint(175, 525), true);
InitPressureButtonGuiElements(virtualPadData.up, NewBitmap(EmbeddedImage<res_upPressed>().Get(), wxPoint(186, 227)), this, wxPoint(175, 565), true);
InitPressureButtonGuiElements(virtualPadData.right, NewBitmap(EmbeddedImage<res_rightPressed>().Get(), wxPoint(248, 302)), this, wxPoint(175, 605), true);
InitPressureButtonGuiElements(virtualPadData.down, NewBitmap(EmbeddedImage<res_downPressed>().Get(), wxPoint(186, 359)), this, wxPoint(175, 645), true);
InitPressureButtonGuiElements(virtualPadData.l1, NewBitmap(EmbeddedImage<res_l1Pressed>().Get(), wxPoint(156, 98)), this, wxPoint(170, 135));
InitPressureButtonGuiElements(virtualPadData.l2, NewBitmap(EmbeddedImage<res_l2Pressed>().Get(), wxPoint(156, 57)), this, wxPoint(170, 18));
InitPressureButtonGuiElements(virtualPadData.l2, NewBitmap(EmbeddedImage<res_l2Pressed>().Get(), wxPoint(156, 57)), this, wxPoint(170, 52), false, true);
InitPressureButtonGuiElements(virtualPadData.r1, NewBitmap(EmbeddedImage<res_r1Pressed>().Get(), wxPoint(921, 98)), this, wxPoint(1035, 135), true);
InitPressureButtonGuiElements(virtualPadData.r2, NewBitmap(EmbeddedImage<res_r2Pressed>().Get(), wxPoint(921, 57)), this, wxPoint(1035, 18), true);
InitPressureButtonGuiElements(virtualPadData.r2, NewBitmap(EmbeddedImage<res_r2Pressed>().Get(), wxPoint(921, 57)), this, wxPoint(1035, 52), true, true);
InitNormalButtonGuiElements(virtualPadData.select, NewBitmap(EmbeddedImage<res_selectPressed>().Get(), wxPoint(457, 313)), this, wxPoint(530, 320));
InitNormalButtonGuiElements(virtualPadData.start, NewBitmap(EmbeddedImage<res_startPressed>().Get(), wxPoint(688, 311)), this, wxPoint(650, 320));
InitNormalButtonGuiElements(virtualPadData.l3, NewBitmap(EmbeddedImage<res_r3Pressed>().Get(), wxPoint(726, 453)), this, wxPoint(440, 835)); // TODO - text for L3 / R3
InitNormalButtonGuiElements(virtualPadData.r3, NewBitmap(EmbeddedImage<res_l3Pressed>().Get(), wxPoint(336, 453)), this, wxPoint(844, 835)); // TODO - text for L3 / R3
InitNormalButtonGuiElements(virtualPadData.select, NewBitmap(EmbeddedImage<res_selectPressed>().Get(), wxPoint(458, 313)), this, wxPoint(530, 315));
InitNormalButtonGuiElements(virtualPadData.start, NewBitmap(EmbeddedImage<res_startPressed>().Get(), wxPoint(688, 311)), this, wxPoint(646, 315));
InitNormalButtonGuiElements(virtualPadData.l3, NewBitmap(EmbeddedImage<res_l3Pressed>().Get(), wxPoint(336, 453)), this, wxPoint(560, 638));
InitNormalButtonGuiElements(virtualPadData.r3, NewBitmap(EmbeddedImage<res_r3Pressed>().Get(), wxPoint(726, 453)), this, wxPoint(615, 638));
InitAnalogStickGuiElements(virtualPadData.leftAnalog, this, wxPoint(405, 522), 101, wxPoint(312, 642), wxPoint(525, 431), false, wxPoint(507, 662), wxPoint(507, 622));
InitAnalogStickGuiElements(virtualPadData.rightAnalog, this, wxPoint(795, 522), 101, wxPoint(703, 642), wxPoint(648, 431), true, wxPoint(695, 662), wxPoint(695, 622), true);
InitAnalogStickGuiElements(virtualPadData.leftAnalog, this, wxPoint(404, 522), 100, wxPoint(314, 642), wxPoint(526, 432), false, wxPoint(504, 685), wxPoint(570, 425), true);
InitAnalogStickGuiElements(virtualPadData.rightAnalog, this, wxPoint(794, 522), 100, wxPoint(706, 642), wxPoint(648, 432), true, wxPoint(700, 685), wxPoint(635, 425));
ignoreRealControllerBox = new wxCheckBox(this, wxID_ANY, wxEmptyString, ScaledPoint(wxPoint(586, 135)), wxDefaultSize);
resetButton = new wxButton(this, wxID_ANY, _("Reset"), ScaledPoint(wxPoint(1195, 5), wxSize(100, 50), true), ScaledSize(wxSize(100, 50)));
ignoreRealControllerBox = new wxCheckBox(this, wxID_ANY, wxEmptyString, ScaledPoint(575, 135), wxDefaultSize);
Bind(wxEVT_CHECKBOX, &VirtualPad::OnIgnoreRealController, this, ignoreRealControllerBox->GetId());
Bind(wxEVT_BUTTON, &VirtualPad::OnResetButton, this, resetButton->GetId());
// Bind Window Events
Bind(wxEVT_MOVE, &VirtualPad::OnMoveAround, this);
@ -116,8 +134,10 @@ VirtualPad::VirtualPad(wxWindow* parent, int controllerPort, AppConfig::InputRec
SetBackgroundColour(*wxWHITE);
SetBackgroundStyle(wxBG_STYLE_PAINT);
// This window does not allow for resizing for sake of simplicity: all images are scaled initially and stored, ready to be rendered
SetWindowStyle(wxDEFAULT_FRAME_STYLE & ~wxRESIZE_BORDER);
SetDoubleBuffered(true);
SetWindowStyle(wxDEFAULT_FRAME_STYLE & ~wxRESIZE_BORDER & ~wxMAXIMIZE_BOX);
// Causes flickering, despite it supposed to be preventing it!
// SetDoubleBuffered(true);
}
void VirtualPad::OnMoveAround(wxMoveEvent& event)
@ -158,30 +178,28 @@ void VirtualPad::OnEraseBackground(wxEraseEvent& event)
void VirtualPad::OnPaint(wxPaintEvent& event)
{
// DevCon.WriteLn("Paint Event Called");
wxPaintDC dc(this);
wxBufferedPaintDC dc(this, wxBUFFER_VIRTUAL_AREA);
Render(dc);
}
void VirtualPad::Redraw()
{
wxClientDC dc(this);
wxClientDC cdc(this);
wxBufferedDC dc(&cdc);
Render(dc);
}
void VirtualPad::Render(wxDC& dc)
void VirtualPad::Render(wxDC& bdc)
{
// Update GUI Elements and figure out what needs to be rendered
for (VirtualPadElement* virtualPadElement : virtualPadElements)
{
virtualPadElement->UpdateGuiElement(renderQueue, clearScreenRequired);
}
// Update Graphic Elements off render stack
// Before we start rendering (if we have to) clear and re-draw the background
if (!manualRedrawMode || clearScreenRequired || !renderQueue.empty())
{
wxBufferedDC bdc(&dc, dc.GetSize());
bdc.SetBrush(*wxRED);
bdc.SetBrush(*wxWHITE);
bdc.DrawRectangle(wxPoint(0, 0), bdc.GetSize());
bdc.SetBrush(wxNullBrush);
bdc.DrawBitmap(virtualPadData.background.image, virtualPadData.background.coords, true);
@ -206,9 +224,7 @@ void VirtualPad::Render(wxDC& dc)
{
VirtualPadElement* element = renderQueue.front();
if (element)
{
element->Render(bdc);
}
renderQueue.pop();
}
}
@ -219,33 +235,34 @@ bool VirtualPad::UpdateControllerData(u16 const bufIndex, PadData* padData)
return virtualPadData.UpdateVirtualPadData(bufIndex, padData, ignoreRealController && !readOnlyMode, readOnlyMode);
}
void VirtualPad::enablePadElements(bool enable)
void VirtualPad::enableUiElements(bool enable)
{
ignoreRealControllerBox->Enable(enable);
resetButton->Enable(enable);
for (VirtualPadElement* virtualPadElement : virtualPadElements)
{
virtualPadElement->EnableWidgets(enable);
}
}
void VirtualPad::SetReadOnlyMode()
void VirtualPad::SetReadOnlyMode(bool readOnly)
{
enablePadElements(false);
readOnlyMode = true;
enableUiElements(!readOnly);
readOnlyMode = readOnly;
}
void VirtualPad::ClearReadOnlyMode()
{
enablePadElements(true);
readOnlyMode = false;
}
void VirtualPad::OnIgnoreRealController(wxCommandEvent const& event)
void VirtualPad::OnIgnoreRealController(wxCommandEvent& event)
{
const wxCheckBox* ignoreButton = (wxCheckBox*)event.GetEventObject();
if (ignoreButton)
{
ignoreRealController = ignoreButton->GetValue();
}
}
void VirtualPad::OnResetButton(wxCommandEvent& event)
{
if (readOnlyMode)
return;
for (VirtualPadElement* virtualPadElement : virtualPadElements)
virtualPadElement->Reset(this);
}
void VirtualPad::OnNormalButtonPress(wxCommandEvent& event)
@ -254,14 +271,10 @@ void VirtualPad::OnNormalButtonPress(wxCommandEvent& event)
ControllerNormalButton* eventBtn = buttonElements[pressedButton->GetId()];
if (pressedButton)
{
eventBtn->pressed = pressedButton->GetValue();
}
if (!eventBtn->isControllerPressBypassed)
{
eventBtn->isControllerPressBypassed = true;
}
}
void VirtualPad::OnPressureButtonPressureChange(wxCommandEvent& event)
@ -270,9 +283,8 @@ void VirtualPad::OnPressureButtonPressureChange(wxCommandEvent& event)
ControllerPressureButton* eventBtn = pressureElements[pressureSpinner->GetId()];
if (pressureSpinner)
{
eventBtn->pressure = pressureSpinner->GetValue();
}
eventBtn->pressed = eventBtn->pressure > 0;
if (!eventBtn->isControllerPressureBypassed || !eventBtn->isControllerPressBypassed)
@ -288,15 +300,12 @@ void VirtualPad::OnAnalogSpinnerChange(wxCommandEvent& event)
AnalogVector* eventVector = analogElements[analogSpinner->GetId()];
if (analogSpinner)
{
eventVector->val = analogSpinner->GetValue();
}
eventVector->slider->SetValue(eventVector->val);
if (!eventVector->isControllerBypassed)
{
eventVector->isControllerBypassed = true;
}
}
void VirtualPad::OnAnalogSliderChange(wxCommandEvent& event)
@ -305,46 +314,57 @@ void VirtualPad::OnAnalogSliderChange(wxCommandEvent& event)
AnalogVector* eventVector = analogElements[analogSlider->GetId()];
if (analogSlider)
{
eventVector->val = analogSlider->GetValue();
}
eventVector->spinner->SetValue(eventVector->val);
if (!eventVector->isControllerBypassed)
{
eventVector->isControllerBypassed = true;
}
}
/// GUI Element Utility Functions
wxPoint VirtualPad::ScaledPoint(wxPoint point, int widgetWidth, bool rightAligned)
bool VirtualPad::floatCompare(float a, float b, float epsilon)
{
return ScaledPoint(point.x, point.y, widgetWidth, rightAligned);
return (fabs(a - b) < epsilon);
}
wxPoint VirtualPad::ScaledPoint(int x, int y, int widgetWidth, bool rightAligned)
wxPoint VirtualPad::ScaledPoint(wxPoint point, wxSize widgetWidth, bool rightAlignedCoord, bool bottomAlignedCoord)
{
return ScaledPoint(point.x, point.y, widgetWidth.x, widgetWidth.y, rightAlignedCoord, bottomAlignedCoord);
}
wxPoint VirtualPad::ScaledPoint(int x, int y, int widgetWidth, int widgetHeight, bool rightAlignedCoord, bool bottomAlignedCoord)
{
wxPoint scaledPoint = wxPoint(x * scalingFactor, y * scalingFactor);
if (rightAligned)
if (rightAlignedCoord)
{
scaledPoint.x -= widgetWidth * scalingFactor;
if (scaledPoint.x < 0)
{
scaledPoint.x = 0;
}
if (bottomAlignedCoord)
{
scaledPoint.y -= widgetHeight * scalingFactor;
if (scaledPoint.y < 0)
scaledPoint.y = 0;
}
return scaledPoint;
}
wxSize VirtualPad::ScaledSize(wxSize size)
{
return ScaledSize(size.x, size.y);
}
wxSize VirtualPad::ScaledSize(int x, int y)
{
return wxSize(x * scalingFactor, y * scalingFactor);
}
ImageFile VirtualPad::NewBitmap(wxImage resource, wxPoint imgCoord)
ImageFile VirtualPad::NewBitmap(wxImage resource, wxPoint imgCoord, bool dontScale)
{
return NewBitmap(scalingFactor, resource, imgCoord);
return NewBitmap(dontScale ? 1 : scalingFactor, resource, imgCoord);
}
ImageFile VirtualPad::NewBitmap(float scalingFactor, wxImage resource, wxPoint imgCoord)
@ -368,11 +388,10 @@ void VirtualPad::InitNormalButtonGuiElements(ControllerNormalButton& button, Ima
virtualPadElements.push_back(&button);
}
void VirtualPad::InitPressureButtonGuiElements(ControllerPressureButton& button, ImageFile image, wxWindow* parentWindow, wxPoint pressureSpinnerCoord, bool rightAlignedCoord)
void VirtualPad::InitPressureButtonGuiElements(ControllerPressureButton& button, ImageFile image, wxWindow* parentWindow, wxPoint pressureSpinnerCoord, bool rightAlignedCoord, bool bottomAlignedCoord)
{
const int spinnerWidth = 100;
const wxPoint scaledPoint = ScaledPoint(pressureSpinnerCoord.x, pressureSpinnerCoord.y, spinnerWidth, rightAlignedCoord);
wxSpinCtrl* spinner = new wxSpinCtrl(parentWindow, wxID_ANY, wxEmptyString, scaledPoint, ScaledSize(spinnerWidth, wxDefaultSize.GetHeight()), wxSP_ARROW_KEYS, 0, 255, 0);
const wxPoint scaledPoint = ScaledPoint(pressureSpinnerCoord, SPINNER_SIZE, rightAlignedCoord, bottomAlignedCoord);
wxSpinCtrl* spinner = new wxSpinCtrl(parentWindow, wxID_ANY, wxEmptyString, scaledPoint, ScaledSize(SPINNER_SIZE), wxSP_ARROW_KEYS, 0, 255, 0);
button.icon = image;
button.pressureSpinner = spinner;
@ -381,7 +400,8 @@ void VirtualPad::InitPressureButtonGuiElements(ControllerPressureButton& button,
virtualPadElements.push_back(&button);
}
void VirtualPad::InitAnalogStickGuiElements(AnalogStick& analog, wxWindow* parentWindow, wxPoint centerPoint, int radius, wxPoint xSliderPoint, wxPoint ySliderPoint, bool flipYSlider, wxPoint xSpinnerPoint, wxPoint ySpinnerPoint, bool rightAlignedSpinners)
void VirtualPad::InitAnalogStickGuiElements(AnalogStick& analog, wxWindow* parentWindow, wxPoint centerPoint, int radius,
wxPoint xSliderPoint, wxPoint ySliderPoint, bool flipYSlider, wxPoint xSpinnerPoint, wxPoint ySpinnerPoint, bool rightAlignedSpinners)
{
AnalogPosition analogPos = AnalogPosition();
analogPos.centerCoords = ScaledPoint(centerPoint);
@ -389,14 +409,15 @@ void VirtualPad::InitAnalogStickGuiElements(AnalogStick& analog, wxWindow* paren
analogPos.radius = radius * scalingFactor;
analogPos.lineThickness = 6 * scalingFactor;
const int spinnerWidth = 90;
const wxPoint xSpinnerScaledPoint = ScaledPoint(xSpinnerPoint, spinnerWidth, rightAlignedSpinners);
const wxPoint ySpinnerScaledPoint = ScaledPoint(ySpinnerPoint, spinnerWidth, rightAlignedSpinners);
const wxPoint xSpinnerScaledPoint = ScaledPoint(xSpinnerPoint, SPINNER_SIZE, rightAlignedSpinners);
const wxPoint ySpinnerScaledPoint = ScaledPoint(ySpinnerPoint, SPINNER_SIZE, rightAlignedSpinners, true);
wxSlider* xSlider = new wxSlider(parentWindow, wxID_ANY, 127, 0, 255, ScaledPoint(xSliderPoint), ScaledSize(185, 30));
wxSlider* ySlider = new wxSlider(parentWindow, wxID_ANY, 127, 0, 255, ScaledPoint(ySliderPoint), ScaledSize(30, 185), flipYSlider ? wxSL_LEFT : wxSL_RIGHT);
wxSpinCtrl* xSpinner = new wxSpinCtrl(parentWindow, wxID_ANY, wxEmptyString, xSpinnerScaledPoint, ScaledSize(90, wxDefaultSize.GetHeight()), wxSP_ARROW_KEYS, 0, 255, 127);
wxSpinCtrl* ySpinner = new wxSpinCtrl(parentWindow, wxID_ANY, wxEmptyString, ySpinnerScaledPoint, ScaledSize(90, wxDefaultSize.GetHeight()), wxSP_ARROW_KEYS, 0, 255, 127);
wxSlider* xSlider = new wxSlider(parentWindow, wxID_ANY, ANALOG_NEUTRAL, 0, ANALOG_MAX,
ScaledPoint(xSliderPoint), ScaledSize(ANALOG_SLIDER_WIDTH, ANALOG_SLIDER_HEIGHT));
wxSlider* ySlider = new wxSlider(parentWindow, wxID_ANY, ANALOG_NEUTRAL, 0, ANALOG_MAX,
ScaledPoint(ySliderPoint), ScaledSize(ANALOG_SLIDER_HEIGHT, ANALOG_SLIDER_WIDTH), flipYSlider ? wxSL_LEFT : wxSL_RIGHT);
wxSpinCtrl* xSpinner = new wxSpinCtrl(parentWindow, wxID_ANY, wxEmptyString, xSpinnerScaledPoint, ScaledSize(SPINNER_SIZE), wxSP_ARROW_KEYS, 0, 255, 127);
wxSpinCtrl* ySpinner = new wxSpinCtrl(parentWindow, wxID_ANY, wxEmptyString, ySpinnerScaledPoint, ScaledSize(SPINNER_SIZE), wxSP_ARROW_KEYS, 0, 255, 127);
analog.xVector.slider = xSlider;
analog.yVector.slider = ySlider;

View File

@ -43,14 +43,21 @@ public:
// - PadData will not be updated if ReadOnly mode is set
// - returns a bool to indicate if the PadData has been updated
bool UpdateControllerData(u16 const bufIndex, PadData* padData);
// Enables ReadOnly mode and disables GUI widgets
void SetReadOnlyMode();
// Disables ReadOnly mode and re-enables GUI widgets
void ClearReadOnlyMode();
// Enables/Disables read only mode and enables/disables GUI widgets
void SetReadOnlyMode(bool readOnly);
// To be called at maximum, once per frame to update widget's value and re-render the VirtualPad's graphics
void Redraw();
private:
/// Constants
const wxSize SPINNER_SIZE = wxSize(100, 40);
static const int ANALOG_SLIDER_WIDTH = 185;
static const int ANALOG_SLIDER_HEIGHT = 30;
static const int PRESSURE_MAX = 255;
static const int ANALOG_NEUTRAL = 127;
static const int ANALOG_MAX = 255;
AppConfig::InputRecordingOptions& options;
bool clearScreenRequired = false;
@ -65,10 +72,11 @@ private:
std::vector<VirtualPadElement*> virtualPadElements;
std::queue<VirtualPadElement*> renderQueue;
void enablePadElements(bool enable);
void enableUiElements(bool enable);
/// GUI Elements
wxCheckBox* ignoreRealControllerBox;
wxButton* resetButton;
std::map<wxWindowID, ControllerNormalButton*> buttonElements;
std::map<wxWindowID, ControllerPressureButton*> pressureElements;
@ -84,21 +92,24 @@ private:
void OnAnalogSliderChange(wxCommandEvent& event);
void OnAnalogSpinnerChange(wxCommandEvent& event);
void OnIgnoreRealController(wxCommandEvent const& event);
void OnIgnoreRealController(wxCommandEvent& event);
void OnNormalButtonPress(wxCommandEvent& event);
void OnPressureButtonPressureChange(wxCommandEvent& event);
void OnResetButton(wxCommandEvent& event);
/// GUI Creation Utility Functions
float scalingFactor = 1.0;
bool floatCompare(float A, float B, float epsilon = 0.005f);
wxSize ScaledSize(wxSize size);
wxSize ScaledSize(int x, int y);
wxPoint ScaledPoint(wxPoint point, int widgetWidth = 0, bool rightAligned = false);
wxPoint ScaledPoint(int x, int y, int widgetWidth = 0, bool rightAligned = false);
wxPoint ScaledPoint(wxPoint point, wxSize widgetSize = wxDefaultSize, bool rightAlignedCoord = false, bool bottomAlignedCoord = false);
wxPoint ScaledPoint(int x, int y, int widgetWidth, int widgetHeight, bool rightAlignedCoord = false, bool bottomAlignedCoord = false);
ImageFile NewBitmap(wxImage resource, wxPoint imgCoord);
ImageFile NewBitmap(wxImage resource, wxPoint imgCoord, bool dontScale = false);
ImageFile NewBitmap(float scalingFactor, wxImage resource, wxPoint imgCoord);
void InitPressureButtonGuiElements(ControllerPressureButton& button, ImageFile image, wxWindow* parentWindow, wxPoint pressureSpinnerCoord, bool rightAlignedCoord = false);
void InitPressureButtonGuiElements(ControllerPressureButton& button, ImageFile image, wxWindow* parentWindow, wxPoint pressureSpinnerCoord, bool rightAlignedCoord = false, bool bottomAlignedCoord = false);
void InitNormalButtonGuiElements(ControllerNormalButton& btn, ImageFile image, wxWindow* parentWindow, wxPoint checkboxCoord);
void InitAnalogStickGuiElements(AnalogStick& analog, wxWindow* parentWindow, wxPoint centerPoint, int radius, wxPoint xSliderPoint,
wxPoint ySliderPoint, bool flipYSlider, wxPoint xSpinnerPoint, wxPoint ySpinnerPoint, bool rightAlignedSpinners = false);

View File

@ -22,21 +22,31 @@
#include "Recording/VirtualPad/VirtualPadResources.h"
#include "Recording/PadData.h"
wxCommandEvent VirtualPadElement::ConstructEvent(wxEventTypeTag<wxCommandEvent> eventType, wxWindow* obj)
{
wxCommandEvent event(eventType, obj->GetId());
event.SetEventObject(obj);
return event;
}
wxCommandEvent VirtualPadElement::ConstructEvent(wxEventTypeTag<wxSpinEvent> eventType, wxWindow* obj)
{
wxCommandEvent event(eventType, obj->GetId());
event.SetEventObject(obj);
return event;
}
void ControllerNormalButton::UpdateGuiElement(std::queue<VirtualPadElement*>& renderQueue, bool& clearScreenRequired)
{
ControllerNormalButton& button = *this;
// This boolean is set when we parse the PadData in VirtualPadData::UpdateVirtualPadData
// Updating wxWidget elements can be expensive, we only want to do this if required
if (button.widgetUpdateRequired)
{
button.pressedBox->SetValue(button.pressed);
}
// We only render the button if it is pressed
if (button.pressed)
{
renderQueue.push(this);
}
// However, if the button has been drawn to the screen in the past
// we need to ensure the screen is cleared.
// This is needed in the scenario where only a single button is being pressed/released
@ -52,14 +62,10 @@ void ControllerPressureButton::UpdateGuiElement(std::queue<VirtualPadElement*>&
{
ControllerPressureButton& button = *this;
if (button.widgetUpdateRequired)
{
button.pressureSpinner->SetValue(button.pressure);
}
if (button.pressed)
{
renderQueue.push(this);
}
else if (button.currentlyRendered)
{
button.currentlyRendered = false;
@ -83,9 +89,7 @@ void AnalogStick::UpdateGuiElement(std::queue<VirtualPadElement*>& renderQueue,
// We render the analog sticks as long as they are not in the neutral position
if (!(analogStick.xVector.val == PadData::ANALOG_VECTOR_NEUTRAL && analogStick.yVector.val == PadData::ANALOG_VECTOR_NEUTRAL))
{
renderQueue.push(this);
}
else if (analogStick.currentlyRendered)
{
analogStick.currentlyRendered = false;
@ -95,47 +99,20 @@ void AnalogStick::UpdateGuiElement(std::queue<VirtualPadElement*>& renderQueue,
void ControllerNormalButton::EnableWidgets(bool enable)
{
ControllerNormalButton& button = *this;
if (enable)
{
button.pressedBox->Enable();
}
else
{
button.pressedBox->Disable();
}
this->pressedBox->Enable(enable);
}
void ControllerPressureButton::EnableWidgets(bool enable)
{
ControllerPressureButton& button = *this;
if (enable)
{
button.pressureSpinner->Enable();
}
else
{
button.pressureSpinner->Disable();
}
this->pressureSpinner->Enable(enable);
}
void AnalogStick::EnableWidgets(bool enable)
{
AnalogStick& analog = *this;
if (enable)
{
analog.xVector.slider->Enable();
analog.yVector.slider->Enable();
analog.xVector.spinner->Enable();
analog.yVector.spinner->Enable();
}
else
{
analog.xVector.slider->Disable();
analog.yVector.slider->Disable();
analog.xVector.spinner->Disable();
analog.yVector.spinner->Disable();
}
this->xVector.slider->Enable(enable);
this->yVector.slider->Enable(enable);
this->xVector.spinner->Enable(enable);
this->yVector.spinner->Enable(enable);
}
void ControllerNormalButton::Render(wxDC& dc)
@ -181,6 +158,30 @@ void AnalogStick::Render(wxDC& dc)
analogStick.currentlyRendered = true;
}
void ControllerNormalButton::Reset(wxEvtHandler* destWindow)
{
this->pressedBox->SetValue(false);
wxPostEvent(destWindow, ConstructEvent(wxEVT_CHECKBOX, this->pressedBox));
}
void ControllerPressureButton::Reset(wxEvtHandler* destWindow)
{
this->pressureSpinner->SetValue(0);
wxPostEvent(destWindow, ConstructEvent(wxEVT_SPINCTRL, this->pressureSpinner));
}
void AnalogStick::Reset(wxEvtHandler* destWindow)
{
this->xVector.slider->SetValue(127);
this->yVector.slider->SetValue(127);
wxPostEvent(destWindow, ConstructEvent(wxEVT_SLIDER, this->xVector.slider));
wxPostEvent(destWindow, ConstructEvent(wxEVT_SLIDER, this->yVector.slider));
this->xVector.spinner->SetValue(127);
this->xVector.spinner->SetValue(127);
wxPostEvent(destWindow, ConstructEvent(wxEVT_SPINCTRL, this->xVector.spinner));
wxPostEvent(destWindow, ConstructEvent(wxEVT_SPINCTRL, this->yVector.spinner));
}
bool ControllerNormalButton::UpdateData(bool& padDataVal, bool ignoreRealController, bool readOnly)
{
return this->UpdateButtonData(padDataVal, ignoreRealController, readOnly);

View File

@ -25,6 +25,7 @@
#include "wx/gdicmn.h"
#include "wx/slider.h"
#include "wx/spinctrl.h"
#include "wx/dcbuffer.h"
struct ImageFile
{
@ -63,8 +64,12 @@ class VirtualPadElement
public:
bool currentlyRendered = false;
wxCommandEvent ConstructEvent(wxEventTypeTag<wxCommandEvent> eventType, wxWindow *obj);
wxCommandEvent ConstructEvent(wxEventTypeTag<wxSpinEvent> eventType, wxWindow *obj);
virtual void EnableWidgets(bool enable) = 0;
virtual void Render(wxDC& dc) = 0;
virtual void Reset(wxEvtHandler* destWindow) = 0;
virtual void UpdateGuiElement(std::queue<VirtualPadElement*>& renderQueue, bool& clearScreenRequired) = 0;
};
@ -88,6 +93,7 @@ public:
bool UpdateData(bool& padDataVal, bool ignoreRealController, bool readOnly);
void EnableWidgets(bool enable) override;
void Render(wxDC& dc) override;
void Reset(wxEvtHandler* destWindow) override;
void UpdateGuiElement(std::queue<VirtualPadElement*>& renderQueue, bool& clearScreenRequired) override;
};
@ -106,6 +112,7 @@ public:
bool UpdateData(u8& padDataVal, bool ignoreRealController, bool readOnly);
void EnableWidgets(bool enable) override;
void Render(wxDC& dc) override;
void Reset(wxEvtHandler* destWindow) override;
void UpdateGuiElement(std::queue<VirtualPadElement*>& renderQueue, bool& clearScreenRequired) override;
};
@ -119,6 +126,7 @@ public:
void EnableWidgets(bool enable) override;
void Render(wxDC& dc) override;
void Reset(wxEvtHandler* destWindow) override;
void UpdateGuiElement(std::queue<VirtualPadElement*>& renderQueue, bool& clearScreenRequired) override;
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View File

@ -466,12 +466,14 @@ void MainEmuFrame::CreateCaptureMenu()
void MainEmuFrame::CreateRecordMenu()
{
#ifndef DISABLE_RECORDING
m_menuRecording.Append(MenuId_Recording_New, _("New"));
m_menuRecording.Append(MenuId_Recording_Stop, _("Stop"))->Enable(false);
m_menuRecording.Append(MenuId_Recording_Play, _("Play"));
m_menuRecording.AppendSeparator();
m_menuRecording.Append(MenuId_Recording_VirtualPad_Port0, _("Virtual Pad (Port 1)"));
m_menuRecording.Append(MenuId_Recording_VirtualPad_Port1, _("Virtual Pad (Port 2)"));
#endif
}
void MainEmuFrame::CreateHelpMenu()

View File

@ -145,7 +145,15 @@
<Outputs>%(RelativeDir)%(Filename).h</Outputs>
</CustomBuild>
<!-- Generate Recording GUI Image Headers -->
<CustomBuild Include="..\..\Recording\VirtualPad\img\controller.png">
<CustomBuild Include="..\..\Recording\VirtualPad\img\controllerFull.png">
<Command>cmd.exe /c %(RelativeDir)bin2cpp.cmd %(Filename)%(Extension)</Command>
<Outputs>%(RelativeDir)%(Filename).h</Outputs>
</CustomBuild>
<CustomBuild Include="..\..\Recording\VirtualPad\img\controllerThreeQuarters.png">
<Command>cmd.exe /c %(RelativeDir)bin2cpp.cmd %(Filename)%(Extension)</Command>
<Outputs>%(RelativeDir)%(Filename).h</Outputs>
</CustomBuild>
<CustomBuild Include="..\..\Recording\VirtualPad\img\controllerHalf.png">
<Command>cmd.exe /c %(RelativeDir)bin2cpp.cmd %(Filename)%(Extension)</Command>
<Outputs>%(RelativeDir)%(Filename).h</Outputs>
</CustomBuild>