recording: New VirtualPad implementation

This commit is contained in:
Tyler Wilding 2020-04-26 19:53:02 -04:00 committed by refractionpcsx2
parent b53d22ae7d
commit 0728acaf55
7 changed files with 926 additions and 40 deletions

View File

@ -1,40 +0,0 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2019 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "PadData.h"
#ifndef DISABLE_RECORDING
class RecordingInputManager
{
public:
RecordingInputManager();
void ControllerInterrupt(u8 &data, u8 &port, u16 & BufCount, u8 buf[]);
// Handles normal keys
void SetButtonState(int port, PadData_NormalButton button, int pressure);
// Handles analog sticks
void UpdateAnalog(int port, PadData_AnalogVector vector, int value);
void SetVirtualPadReading(int port, bool read);
protected:
PadData pad;
bool virtualPad[2];
};
extern RecordingInputManager g_RecordingInput;
#endif

View File

@ -0,0 +1,386 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2019 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "math.h"
#include "App.h"
#include "Common.h"
#include "MSWstuff.h"
#include "Recording/VirtualPad/VirtualPad.h"
#include "Recording/VirtualPad/VirtualPadResources.h"
#include "Recording/VirtualPad/img/controller.h"
#include "Recording/VirtualPad/img/circlePressed.h"
#include "Recording/VirtualPad/img/crossPressed.h"
#include "Recording/VirtualPad/img/squarePressed.h"
#include "Recording/VirtualPad/img/trianglePressed.h"
#include "Recording/VirtualPad/img/downPressed.h"
#include "Recording/VirtualPad/img/leftPressed.h"
#include "Recording/VirtualPad/img/rightPressed.h"
#include "Recording/VirtualPad/img/upPressed.h"
#include "Recording/VirtualPad/img/l1Pressed.h"
#include "Recording/VirtualPad/img/l2Pressed.h"
#include "Recording/VirtualPad/img/r1Pressed.h"
#include "Recording/VirtualPad/img/r2Pressed.h"
#include "Recording/VirtualPad/img/selectPressed.h"
#include "Recording/VirtualPad/img/startPressed.h"
#include "Recording/VirtualPad/img/l3Pressed.h"
#include "Recording/VirtualPad/img/r3Pressed.h"
// TODO - store position of frame in ini file?
VirtualPad::VirtualPad(wxWindow* parent, wxWindowID id, const wxString& title, int controllerPort, const wxPoint& pos, const wxSize& size, long style) :
wxFrame(parent, id, title, pos, size, wxDEFAULT_FRAME_STYLE)
{
// 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.
// 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));
wxRect screen = display.GetClientArea();
if (screen.height > 1080 && screen.height <= 1440) // 1440p display
{
scalingFactor = 0.75;
}
else if (screen.height <= 1080) // 1080p display
{
scalingFactor = 0.5;
} // otherwise use default 1.0 scaling
virtualPadData = VirtualPadData();
virtualPadData.background = NewBitmap(EmbeddedImage<res_controller>().Get(), wxPoint(0, 0));
// Use the background image's size to define the window size
SetClientSize(virtualPadData.background.width, virtualPadData.background.height);
// TODO - rename to Gui
InitPressureButtonGUIElements(virtualPadData.cross, NewBitmap(EmbeddedImage<res_crossPressed>().Get(), wxPoint(968, 498)), this, wxPoint(1062, 660));
InitPressureButtonGUIElements(virtualPadData.circle, NewBitmap(EmbeddedImage<res_circlePressed>().Get(), wxPoint(1057, 413)), this, wxPoint(1062, 700));
InitPressureButtonGUIElements(virtualPadData.triangle, NewBitmap(EmbeddedImage<res_trianglePressed>().Get(), wxPoint(968, 325)), this, wxPoint(1062, 740));
InitPressureButtonGUIElements(virtualPadData.square, NewBitmap(EmbeddedImage<res_squarePressed>().Get(), wxPoint(879, 413)), this, wxPoint(1062, 780));
InitPressureButtonGUIElements(virtualPadData.down, NewBitmap(EmbeddedImage<res_downPressed>().Get(), wxPoint(191, 488)), this, wxPoint(199, 660), true);
InitPressureButtonGUIElements(virtualPadData.right, NewBitmap(EmbeddedImage<res_rightPressed>().Get(), wxPoint(255, 429)), this, wxPoint(199, 700), true);
InitPressureButtonGUIElements(virtualPadData.up, NewBitmap(EmbeddedImage<res_upPressed>().Get(), wxPoint(191, 354)), this, wxPoint(199, 740), true);
InitPressureButtonGUIElements(virtualPadData.left, NewBitmap(EmbeddedImage<res_leftPressed>().Get(), wxPoint(115, 429)), this, wxPoint(199, 780), true);
InitPressureButtonGUIElements(virtualPadData.l1, NewBitmap(EmbeddedImage<res_l1Pressed>().Get(), wxPoint(166, 8)), this, wxPoint(294, 20));
InitPressureButtonGUIElements(virtualPadData.l2, NewBitmap(EmbeddedImage<res_l2Pressed>().Get(), wxPoint(166, 81)), this, wxPoint(294, 100));
InitPressureButtonGUIElements(virtualPadData.r1, NewBitmap(EmbeddedImage<res_r1Pressed>().Get(), wxPoint(958, 7)), this, wxPoint(940, 20), true);
InitPressureButtonGUIElements(virtualPadData.r2, NewBitmap(EmbeddedImage<res_r2Pressed>().Get(), wxPoint(958, 81)), this, wxPoint(940, 100), true);
InitNormalButtonGUIElements(virtualPadData.select, NewBitmap(EmbeddedImage<res_selectPressed>().Get(), wxPoint(473, 441)), this, wxPoint(545, 448));
InitNormalButtonGUIElements(virtualPadData.start, NewBitmap(EmbeddedImage<res_startPressed>().Get(), wxPoint(710, 440)), this, wxPoint(675, 448));
InitNormalButtonGUIElements(virtualPadData.l3, NewBitmap(EmbeddedImage<res_r3Pressed>().Get(), wxPoint(347, 585)), this, wxPoint(440, 835));
InitNormalButtonGUIElements(virtualPadData.r3, NewBitmap(EmbeddedImage<res_l3Pressed>().Get(), wxPoint(750, 585)), this, wxPoint(844, 835));
InitAnalogStickGuiElements(virtualPadData.leftAnalog, this, wxPoint(418, 656), 105, wxPoint(326, 782), wxPoint(545, 568), false, wxPoint(522, 800), wxPoint(522, 760));
InitAnalogStickGuiElements(virtualPadData.rightAnalog, this, wxPoint(821, 656), 105, wxPoint(730, 782), wxPoint(672, 568), true, wxPoint(720, 800), wxPoint(720, 760), true);
ignoreRealControllerBox = new wxCheckBox(this, wxID_ANY, wxEmptyString, NewScaledPoint(605, 256), wxDefaultSize);
Bind(wxEVT_CHECKBOX, &VirtualPad::OnIgnoreRealController, this, ignoreRealControllerBox->GetId());
// Bind Window Events
Bind(wxEVT_ERASE_BACKGROUND, &VirtualPad::OnEraseBackground, this);
Bind(wxEVT_MOTION, &VirtualPad::OnMouseEvent, this);
Bind(wxEVT_CLOSE_WINDOW, &VirtualPad::OnClose, this);
Bind(wxEVT_SET_FOCUS, &VirtualPad::OnFocusEvent, this);
Bind(wxEVT_KILL_FOCUS, &VirtualPad::OnFocusEvent, this);
// Temporary Paint event handler so the window displays properly before the controller-interrupt routine takes over with manual drawing.
// The reason for this is in order to minimize the performance impact, we need total control over when render is called
// Windows redraws the window _alot_ otherwise which leads to major performance problems
Bind(wxEVT_PAINT, &VirtualPad::OnPaint, this);
// Finalize layout
SetIcons(wxGetApp().GetIconBundle());
SetTitle(wxString::Format("Virtual Pad - Port %d", controllerPort + 1));
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(style & ~wxRESIZE_BORDER);
SetDoubleBuffered(true);
}
// TODO - test open/close routine
void VirtualPad::OnClose(wxCloseEvent & event)
{
// Re-bind the Paint event in case this is due to a game being opened/closed
manualRedrawMode = false;
Bind(wxEVT_PAINT, &VirtualPad::OnPaint, this);
Hide(); // TODO - hide vs closed?
}
void VirtualPad::OnMouseEvent(wxMouseEvent &evt)
{
evt.Skip();
return;
}
void VirtualPad::OnFocusEvent(wxFocusEvent &evt)
{
evt.Skip();
return;
}
void VirtualPad::OnEraseBackground(wxEraseEvent& event)
{
// Intentionally Empty
// See - https://wiki.wxwidgets.org/Flicker-Free_Drawing
}
void VirtualPad::OnPaint(wxPaintEvent &evt)
{
wxPaintDC dc(this);
Render(dc);
}
void VirtualPad::Redraw()
{
// This function is called once per frame once we start receiving data from the controller
// and there is actually an update that needs to be made.
// Once that occurs, we want total control over how often we re-render to minimize flickering
// and minimize the performance impact
if (!manualRedrawMode) {
Unbind(wxEVT_PAINT, &VirtualPad::OnPaint, this);
manualRedrawMode = true;
}
wxClientDC dc(this);
Render(dc);
}
void VirtualPad::Render(wxDC &dc)
{
// Update GUI Elements and figure out what needs to be rendered
virtualPadData.circle.UpdateGuiElement(&renderQueue, clearScreenRequired);
virtualPadData.cross.UpdateGuiElement(&renderQueue, clearScreenRequired);
virtualPadData.square.UpdateGuiElement(&renderQueue, clearScreenRequired);
virtualPadData.triangle.UpdateGuiElement(&renderQueue, clearScreenRequired);
virtualPadData.down.UpdateGuiElement(&renderQueue, clearScreenRequired);
virtualPadData.left.UpdateGuiElement(&renderQueue, clearScreenRequired);
virtualPadData.right.UpdateGuiElement(&renderQueue, clearScreenRequired);
virtualPadData.up.UpdateGuiElement(&renderQueue, clearScreenRequired);
virtualPadData.l1.UpdateGuiElement(&renderQueue, clearScreenRequired);
virtualPadData.l2.UpdateGuiElement(&renderQueue, clearScreenRequired);
virtualPadData.r1.UpdateGuiElement(&renderQueue, clearScreenRequired);
virtualPadData.r2.UpdateGuiElement(&renderQueue, clearScreenRequired);
virtualPadData.select.UpdateGuiElement(&renderQueue, clearScreenRequired);
virtualPadData.start.UpdateGuiElement(&renderQueue, clearScreenRequired);
virtualPadData.l3.UpdateGuiElement(&renderQueue, clearScreenRequired);
virtualPadData.r3.UpdateGuiElement(&renderQueue, clearScreenRequired);
virtualPadData.leftAnalog.UpdateGuiElement(&renderQueue, clearScreenRequired);
virtualPadData.rightAnalog.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.DrawRectangle(wxPoint(0, 0), bdc.GetSize());
bdc.SetBrush(wxNullBrush);
bdc.DrawBitmap(virtualPadData.background.image, virtualPadData.background.coords, true);
clearScreenRequired = false;
while (!renderQueue.empty()) {
VirtualPadElement *element = renderQueue.front();
element->Render(bdc);
renderQueue.pop();
}
}
}
bool VirtualPad::UpdateControllerData(u16 const bufIndex, PadData *padData, bool readOnly)
{
return virtualPadData.UpdateVirtualPadData(bufIndex, padData, ignoreRealController && !readOnly, readOnly);
}
void VirtualPad::OnIgnoreRealController(wxCommandEvent &event)
{
wxCheckBox* ignoreButton = (wxCheckBox*) event.GetEventObject();
ignoreRealController = ignoreButton->GetValue();
}
void VirtualPad::OnNormalButtonPress(wxCommandEvent &event)
{
wxCheckBox* pressedButton = (wxCheckBox*) event.GetEventObject();
ControllerNormalButton *eventBtn = buttonElements[pressedButton->GetId()];
eventBtn->pressed = pressedButton->GetValue();
// If the real controller is being bypassed, we move on, otherwise we begin bypassing the controller
if (!eventBtn->isControllerBypassed) {
eventBtn->isControllerBypassed = true;
}
}
void VirtualPad::OnPressureButtonPressureChange(wxCommandEvent &event)
{
wxSpinCtrl* pressureSpinner = (wxSpinCtrl*) event.GetEventObject();
ControllerPressureButton *eventBtn = pressureElements[pressureSpinner->GetId()];
eventBtn->pressure = pressureSpinner->GetValue();
eventBtn->pressed = eventBtn->pressure > 0;
// If the real controller is being bypassed, we move on, otherwise we begin bypassing the controller
if (!eventBtn->isControllerPressureBypassed || !eventBtn->isControllerPressBypassed) {
eventBtn->isControllerPressureBypassed = true;
eventBtn->isControllerPressBypassed = true;
}
}
void VirtualPad::OnAnalogSpinnerChange(wxCommandEvent &event)
{
wxSpinCtrl* analogSpinner = (wxSpinCtrl*) event.GetEventObject();
AnalogVector *eventVector = analogElements[analogSpinner->GetId()];
eventVector->val = analogSpinner->GetValue();
eventVector->slider->SetValue(eventVector->val);
// If the real controller is being bypassed, we move on, otherwise we begin bypassing the controller
if (!eventVector->isControllerBypassed) {
eventVector->isControllerBypassed = true;
}
}
void VirtualPad::OnAnalogSliderChange(wxCommandEvent &event)
{
wxSlider* analogSlider = (wxSlider*) event.GetEventObject();
AnalogVector *eventVector = analogElements[analogSlider->GetId()];
eventVector->val = analogSlider->GetValue();
eventVector->spinner->SetValue(eventVector->val);
// If the real controller is being bypassed, we move on, otherwise we begin bypassing the controller
if (!eventVector->isControllerBypassed) {
eventVector->isControllerBypassed = true;
}
}
/// GUI Element Utility Functions
wxPoint VirtualPad::NewScaledPoint(wxPoint point)
{
return wxPoint(point.x * scalingFactor, point.y * scalingFactor);
}
wxPoint VirtualPad::NewScaledPoint(int x, int y)
{
return wxPoint(x * scalingFactor, y * scalingFactor);
}
ImageFile VirtualPad::NewBitmap(wxImage resource, wxPoint point)
{
return NewBitmap(scalingFactor, resource, point);
}
ImageFile VirtualPad::NewBitmap(float scalingFactor, wxImage resource, wxPoint point)
{
wxBitmap bitmap = wxBitmap(resource.Rescale(resource.GetWidth() * scalingFactor, resource.GetHeight() * scalingFactor, wxIMAGE_QUALITY_HIGH));
ImageFile image = ImageFile();
image.image = bitmap;
image.width = bitmap.GetWidth();
image.height = bitmap.GetHeight();
image.coords = NewScaledPoint(point);
return image;
}
void VirtualPad::InitNormalButtonGUIElements(ControllerNormalButton &button, ImageFile image, wxWindow *parentWindow, wxPoint point)
{
button.icon = image;
button.pressedBox = new wxCheckBox(parentWindow, wxID_ANY, wxEmptyString, NewScaledPoint(point), wxDefaultSize);
button.isControllerBypassed = false;
button.prevPressedVal = false;
Bind(wxEVT_CHECKBOX, &VirtualPad::OnNormalButtonPress, this, button.pressedBox->GetId());
buttonElements[button.pressedBox->GetId()] = &button;
}
void VirtualPad::InitPressureButtonGUIElements(ControllerPressureButton &button, ImageFile image, wxWindow *parentWindow, wxPoint point, bool rightAlignedPoint)
{
wxPoint scaledPoint = wxPoint(point.x * scalingFactor, point.y * scalingFactor);
if (rightAlignedPoint) {
scaledPoint.x -= 100 * scalingFactor;
if (scaledPoint.x < 0) {
scaledPoint.x = 0;
}
}
wxSpinCtrl *spinner = new wxSpinCtrl(parentWindow, wxID_ANY, wxEmptyString, scaledPoint, wxSize(100 * scalingFactor, wxDefaultSize.GetHeight()), wxSP_ARROW_KEYS, 0, 255, 0);
// TODO - defaults on the classes' constructor would clean this up
button.icon = image;
button.prevPressedVal = 0;
button.pressureSpinner = spinner;
button.isControllerPressBypassed = false;
button.isControllerPressureBypassed = false;
button.prevPressedVal = false;
button.prevPressureVal = 0;
Bind(wxEVT_SPINCTRL, &VirtualPad::OnPressureButtonPressureChange, this, button.pressureSpinner->GetId());
pressureElements[button.pressureSpinner->GetId()] = &button;
}
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 = NewScaledPoint(centerPoint);
analogPos.endCoords = NewScaledPoint(centerPoint);
analogPos.radius = radius * scalingFactor;
analogPos.lineThickness = 6 * scalingFactor;
// TODO - make a function to scale wxSize values easier
wxSlider *xSlider = new wxSlider(parentWindow, wxID_ANY, 127, 0, 255, NewScaledPoint(xSliderPoint), wxSize(185 * scalingFactor, 30 * scalingFactor));
wxSlider *ySlider = new wxSlider(parentWindow, wxID_ANY, 127, 0, 255, NewScaledPoint(ySliderPoint), wxSize(30 * scalingFactor, 185 * scalingFactor), flipYSlider ? wxSL_LEFT : wxSL_RIGHT);
// TODO - function to right-align spinners easier
wxPoint xSpinnerScaledPoint = NewScaledPoint(xSpinnerPoint);
wxPoint ySpinnerScaledPoint = NewScaledPoint(ySpinnerPoint);
if (rightAlignedSpinners) {
xSpinnerScaledPoint.x -= 90 * scalingFactor;
if (xSpinnerScaledPoint.x < 0) {
xSpinnerScaledPoint.x = 0;
}
ySpinnerScaledPoint.x -= 90 * scalingFactor;
if (ySpinnerScaledPoint.x < 0) {
ySpinnerScaledPoint.x = 0;
}
}
wxSpinCtrl *xSpinner = new wxSpinCtrl(parentWindow, wxID_ANY, wxEmptyString, xSpinnerScaledPoint, wxSize(90 * scalingFactor, wxDefaultSize.GetHeight()), wxSP_ARROW_KEYS, 0, 255, 127);
wxSpinCtrl *ySpinner = new wxSpinCtrl(parentWindow, wxID_ANY, wxEmptyString, ySpinnerScaledPoint, wxSize(90 * scalingFactor, wxDefaultSize.GetHeight()), wxSP_ARROW_KEYS, 0, 255, 127);
analog.xVector.slider = xSlider;
analog.yVector.slider = ySlider;
analog.xVector.spinner = xSpinner;
analog.yVector.spinner = ySpinner;
analog.positionGraphic = analogPos;
Bind(wxEVT_SPINCTRL, &VirtualPad::OnAnalogSpinnerChange, this, analog.xVector.spinner->GetId());
Bind(wxEVT_SPINCTRL, &VirtualPad::OnAnalogSpinnerChange, this, analog.yVector.spinner->GetId());
Bind(wxEVT_SLIDER, &VirtualPad::OnAnalogSliderChange, this, analog.xVector.slider->GetId());
Bind(wxEVT_SLIDER, &VirtualPad::OnAnalogSliderChange, this, analog.yVector.slider->GetId());
analogElements[analog.xVector.spinner->GetId()] = &analog.xVector;
analogElements[analog.yVector.spinner->GetId()] = &analog.yVector;
analogElements[analog.xVector.slider->GetId()] = &analog.xVector;
analogElements[analog.yVector.slider->GetId()] = &analog.yVector;
}

View File

@ -0,0 +1,98 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2019 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <map>
#include <queue>
#include <wx/wx.h>
#include <wx/display.h>
#include <wx/tglbtn.h>
#include <wx/spinctrl.h>
#include <wx/dcbuffer.h>
#include <wx/event.h>
#include "Utilities/EmbeddedImage.h"
#include "Recording/PadData.h"
#include "Recording/VirtualPad/VirtualPadData.h"
#include "Recording/VirtualPad/VirtualPadResources.h"
class VirtualPad : public wxFrame
{
public:
VirtualPad(wxWindow *parent, wxWindowID id, const wxString& title, int controllerPort, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = wxDEFAULT_FRAME_STYLE);
// Updates the VirtualPad if necessary, as well as updates the PadData fields if the VirtualPad is actively overriding them
bool UpdateControllerData(u16 const bufIndex, PadData *padData, bool readOnly = false);
void Redraw();
private:
bool manualRedrawMode = false;
bool clearScreenRequired = false;
void RedrawBackground(wxDC &dc, ImageFile &img);
std::queue<VirtualPadElement*> renderQueue;
/// GUI Creation Utility Functions
float scalingFactor = 1.0;
wxPoint NewScaledPoint(wxPoint point);
wxPoint NewScaledPoint(int x, int y);
ImageFile NewBitmap(wxImage resource, wxPoint point);
ImageFile NewBitmap(float scalingFactor, wxImage resource, wxPoint point);
void InitPressureButtonGUIElements(ControllerPressureButton &button, ImageFile image, wxWindow *parentWindow, wxPoint point, bool rightAlignedPoint = false);
void InitNormalButtonGUIElements(ControllerNormalButton &btn, ImageFile image, wxWindow *parentWindow, wxPoint point);
void InitAnalogStickGuiElements(AnalogStick &analog, wxWindow *parentWindow, wxPoint centerPoint, int radius, wxPoint xSliderPoint, wxPoint ySliderPoint, bool flipYSlider, wxPoint xSpinnerPoint, wxPoint ySpinnerPoint, bool rightAlignedSpinners = false);
/// GUI Elements
wxCheckBox *ignoreRealControllerBox;
// Data
std::map<wxWindowID, ControllerNormalButton*> buttonElements;
std::map<wxWindowID, ControllerPressureButton*> pressureElements;
std::map<wxWindowID, AnalogVector*> analogElements;
// TODO - analog stick resolver might need to be a tuple of the slider/spinctrl
bool ignoreRealController = false;
VirtualPadData virtualPadData;
bool renderGraphics = false;
int imgWrites = 0;
int analogWrites = 0;
// Events
void OnEraseBackground(wxEraseEvent& event);
void OnPaint(wxPaintEvent & evt);
void Render(wxDC& dc);
void OnClose(wxCloseEvent &event);
void OnShow(wxShowEvent &event);
void OnMouseEvent(wxMouseEvent &event);
void OnFocusEvent(wxFocusEvent &event);
void UpdateVirtualPadComponent(wxDC &dc, ControllerNormalButton &btn);
void UpdateVirtualPadComponent(wxDC &dc, ControllerPressureButton &btn);
void DrawImageFile(wxDC &dc, ImageFile &imgFile);
void UpdateVirtualPadComponent(wxDC &dc, AnalogStick &analogStick);
void OnNormalButtonPress(wxCommandEvent &event);
void OnPressureButtonPressureChange(wxCommandEvent &event);
void OnAnalogSliderChange(wxCommandEvent &event);
void OnAnalogSpinnerChange(wxCommandEvent &event);
void OnIgnoreRealController(wxCommandEvent &event);
};

View File

@ -0,0 +1,80 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2020 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "Recording/VirtualPad/VirtualPadData.h"
bool VirtualPadData::UpdateVirtualPadData(u16 bufIndex, PadData *padData, bool ignoreRealController, bool readOnly)
{
bool changeDetected = false;
PadData::BufferIndex index = static_cast<PadData::BufferIndex>(bufIndex);
switch (index)
{
case PadData::BufferIndex::PressedFlagsGroupOne:
changeDetected |= left.UpdateData(padData->leftPressed, ignoreRealController, readOnly);
changeDetected |= down.UpdateData(padData->downPressed, ignoreRealController, readOnly);
changeDetected |= right.UpdateData(padData->rightPressed, ignoreRealController, readOnly);
changeDetected |= up.UpdateData(padData->upPressed, ignoreRealController, readOnly);
changeDetected |= start.UpdateData(padData->start, ignoreRealController, readOnly);
changeDetected |= r3.UpdateData(padData->r3, ignoreRealController, readOnly);
changeDetected |= l3.UpdateData(padData->l3, ignoreRealController, readOnly);
changeDetected |= select.UpdateData(padData->select, ignoreRealController, readOnly);
return changeDetected;
case PadData::BufferIndex::PressedFlagsGroupTwo:
changeDetected |= square.UpdateData(padData->squarePressed, ignoreRealController, readOnly);
changeDetected |= cross.UpdateData(padData->crossPressed, ignoreRealController, readOnly);
changeDetected |= circle.UpdateData(padData->circlePressed, ignoreRealController, readOnly);
changeDetected |= triangle.UpdateData(padData->trianglePressed, ignoreRealController, readOnly);
changeDetected |= r1.UpdateData(padData->r1Pressed, ignoreRealController, readOnly);
changeDetected |= l1.UpdateData(padData->l1Pressed, ignoreRealController, readOnly);
changeDetected |= r2.UpdateData(padData->r2Pressed, ignoreRealController, readOnly);
changeDetected |= l2.UpdateData(padData->l2Pressed, ignoreRealController, readOnly);
return changeDetected;
case PadData::BufferIndex::RightAnalogXVector:
return rightAnalog.xVector.UpdateData(padData->rightAnalogX, ignoreRealController, readOnly);
case PadData::BufferIndex::RightAnalogYVector:
return rightAnalog.yVector.UpdateData(padData->rightAnalogY, ignoreRealController, readOnly);
case PadData::BufferIndex::LeftAnalogXVector:
return leftAnalog.xVector.UpdateData(padData->leftAnalogX, ignoreRealController, readOnly);
case PadData::BufferIndex::LeftAnalogYVector:
return leftAnalog.yVector.UpdateData(padData->leftAnalogY, ignoreRealController, readOnly);
case PadData::BufferIndex::RightPressure:
return right.UpdateData(padData->rightPressure, ignoreRealController, readOnly);
case PadData::BufferIndex::LeftPressure:
return left.UpdateData(padData->leftPressure, ignoreRealController, readOnly);
case PadData::BufferIndex::UpPressure:
return up.UpdateData(padData->upPressure, ignoreRealController, readOnly);
case PadData::BufferIndex::DownPressure:
return down.UpdateData(padData->downPressure, ignoreRealController, readOnly);
case PadData::BufferIndex::TrianglePressure:
return triangle.UpdateData(padData->trianglePressure, ignoreRealController, readOnly);
case PadData::BufferIndex::CirclePressure:
return circle.UpdateData(padData->circlePressure, ignoreRealController, readOnly);
case PadData::BufferIndex::CrossPressure:
return cross.UpdateData(padData->crossPressure, ignoreRealController, readOnly);
case PadData::BufferIndex::SquarePressure:
return square.UpdateData(padData->squarePressure, ignoreRealController, readOnly);
case PadData::BufferIndex::L1Pressure:
return l1.UpdateData(padData->l1Pressure, ignoreRealController, readOnly);
case PadData::BufferIndex::R1Pressure:
return r1.UpdateData(padData->r1Pressure, ignoreRealController, readOnly);
case PadData::BufferIndex::L2Pressure:
return l2.UpdateData(padData->l2Pressure, ignoreRealController, readOnly);
case PadData::BufferIndex::R2Pressure:
return r2.UpdateData(padData->r2Pressure, ignoreRealController, readOnly);
}
return changeDetected;
}

View File

@ -0,0 +1,53 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2020 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "Recording/VirtualPad/VirtualPadResources.h"
#include "Recording/PadData.h"
class VirtualPadData
{
public:
/// Controller Background
ImageFile background;
/// Pressure Buttons
ControllerPressureButton circle;
ControllerPressureButton cross;
ControllerPressureButton square;
ControllerPressureButton triangle;
ControllerPressureButton down;
ControllerPressureButton left;
ControllerPressureButton right;
ControllerPressureButton up;
ControllerPressureButton l1;
ControllerPressureButton l2;
ControllerPressureButton r1;
ControllerPressureButton r2;
/// Normal (un)pressed buttons
ControllerNormalButton select;
ControllerNormalButton start;
ControllerNormalButton l3;
ControllerNormalButton r3;
/// Analog Sticks
AnalogStick leftAnalog;
AnalogStick rightAnalog;
// Given the input buffer and the current index, updates the correct field(s)
bool UpdateVirtualPadData(u16 bufIndex, PadData *padData, bool ignoreRealController = false, bool readOnly = false);
};

View File

@ -0,0 +1,189 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2019 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "Recording/VirtualPad/VirtualPadResources.h"
void ControllerNormalButton::UpdateGuiElement(std::queue<VirtualPadElement*> *renderQueue, bool &clearScreenRequired)
{
ControllerNormalButton &button = *this;
if (button.renderRequired)
{
button.pressedBox->SetValue(button.pressed);
clearScreenRequired = true;
}
if (button.pressed)
{
renderQueue->push(this);
}
}
void ControllerPressureButton::UpdateGuiElement(std::queue<VirtualPadElement *> *renderQueue, bool &clearScreenRequired)
{
ControllerPressureButton &button = *this;
if (button.renderRequired)
{
button.pressureSpinner->SetValue(button.pressure);
clearScreenRequired = true;
}
if (button.pressed) {
renderQueue->push(this);
}
}
void AnalogStick::UpdateGuiElement(std::queue<VirtualPadElement *> *renderQueue, bool &clearScreenRequired)
{
AnalogStick &analogStick = *this;
// Update the GUI elements that need updating
// If either vector has changed, we need to redraw the graphics
if (analogStick.xVector.renderRequired)
{
analogStick.xVector.slider->SetValue(analogStick.xVector.val);
analogStick.xVector.spinner->SetValue(analogStick.xVector.val);
clearScreenRequired = true;
}
if (analogStick.yVector.renderRequired)
{
analogStick.yVector.slider->SetValue(analogStick.yVector.val);
analogStick.yVector.spinner->SetValue(analogStick.yVector.val);
clearScreenRequired = true;
}
// TODO constant for neutral position
if (!(analogStick.xVector.val == 127 && analogStick.yVector.val == 127))
{
renderQueue->push(this);
}
}
void ControllerNormalButton::Render(wxDC &dc)
{
ControllerNormalButton &button = *this;
ImageFile &img = button.icon;
dc.DrawBitmap(img.image, img.coords, true);
}
void ControllerPressureButton::Render(wxDC &dc)
{
ControllerPressureButton &button = *this;
ImageFile &img = button.icon;
dc.DrawBitmap(img.image, img.coords, true);
}
void AnalogStick::Render(wxDC &dc)
{
AnalogStick &analogStick = *this;
// Render graphic
AnalogPosition analogPos = analogStick.positionGraphic;
// Determine new end coordinates
int newXCoord = analogPos.centerCoords.x + ((analogStick.xVector.val - 127) / 127.0) * analogPos.radius;
int newYCoord = analogPos.centerCoords.y + ((analogStick.yVector.val - 127) / 127.0) * analogPos.radius;
// We want to ensure the line segment length is capped at the defined radius
float lengthOfLine = sqrt(pow(newXCoord - analogPos.centerCoords.x, 2) + pow(newYCoord - analogPos.centerCoords.y, 2));
if (lengthOfLine > analogPos.radius) {
newXCoord = ((1 - analogPos.radius / lengthOfLine) * analogPos.centerCoords.x) + analogPos.radius / lengthOfLine * newXCoord;
newYCoord = ((1 - analogPos.radius / lengthOfLine) * analogPos.centerCoords.y) + analogPos.radius / lengthOfLine * newYCoord;
}
// Set the new end coordinate
analogPos.endCoords = wxPoint(newXCoord, newYCoord);
// Draw line and tip
dc.SetPen(wxPen(*wxBLUE, analogPos.lineThickness));
dc.DrawLine(analogPos.centerCoords, analogPos.endCoords);
dc.DrawCircle(analogPos.endCoords, wxCoord(analogPos.lineThickness));
dc.SetPen(wxNullPen);
}
// TODO - duplicate code between this and the pressure button, inheritance should be able to remove it
bool ControllerNormalButton::UpdateData(bool &padDataVal, bool ignoreRealController, bool readOnly)
{
ControllerNormalButton &button = *this;
if (!ignoreRealController) {
// If controller is being bypassed and controller's state has changed
bool bypassedWithChangedState = button.isControllerBypassed && padDataVal != button.prevPressedVal;
if (bypassedWithChangedState) {
button.prevPressedVal = padDataVal;
button.isControllerBypassed = false;
}
// If we aren't bypassing the controller OR the previous condition was met
if (bypassedWithChangedState || !button.isControllerBypassed) {
button.renderRequired = button.pressed != padDataVal;
button.pressed = padDataVal;
return false;
}
}
button.prevPressedVal = padDataVal;
padDataVal = button.pressed;
return button.prevPressedVal != button.pressed;
}
bool ControllerPressureButton::UpdateData(bool &padDataVal, bool ignoreRealController, bool readOnly)
{
ControllerPressureButton &button = *this;
if (!ignoreRealController) {
bool bypassedWithChangedState = button.isControllerPressBypassed && padDataVal != button.prevPressedVal;
if (bypassedWithChangedState) {
button.prevPressedVal = padDataVal;
button.isControllerPressBypassed = false;
}
if (bypassedWithChangedState || !button.isControllerPressBypassed) {
button.renderRequired = button.pressed != padDataVal;
button.pressed = padDataVal;
return false;
}
}
button.prevPressedVal = padDataVal;
padDataVal = button.pressed;
return button.prevPressedVal != button.pressed;
}
bool ControllerPressureButton::UpdateData(u8 &padDataVal, bool ignoreRealController, bool readOnly)
{
ControllerPressureButton &button = *this;
if (!ignoreRealController) {
bool bypassedWithChangedState = button.isControllerPressureBypassed && padDataVal != button.prevPressureVal;
if (bypassedWithChangedState) {
button.prevPressureVal = padDataVal;
button.isControllerPressureBypassed = false;
}
if (bypassedWithChangedState || !button.isControllerPressureBypassed) {
button.renderRequired = button.pressure != padDataVal;
button.pressure = padDataVal;
return false;
}
}
button.prevPressureVal = padDataVal;
padDataVal = button.pressure;
return button.prevPressedVal != button.pressure;
}
bool AnalogVector::UpdateData(u8 &padDataVal, bool ignoreRealController, bool readOnly)
{
AnalogVector &vector = *this;
if (!ignoreRealController) {
bool bypassedWithChangedState = vector.isControllerBypassed && padDataVal != vector.prevVal;
if (bypassedWithChangedState) {
vector.prevVal = padDataVal;
vector.isControllerBypassed = false;
}
if (bypassedWithChangedState || !vector.isControllerBypassed) {
vector.renderRequired = vector.val != padDataVal;
vector.val = padDataVal;
return false;
}
}
vector.prevVal = padDataVal;
padDataVal = vector.val;
return vector.prevVal != vector.val;
}

View File

@ -0,0 +1,120 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2019 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <queue>
#include <wx/wx.h>
#include <wx/tglbtn.h>
#include <wx/spinctrl.h>
#include <wx/dcbuffer.h>
#include <wx/event.h>
#include "Common.h"
struct ImageFile
{
wxBitmap image;
wxPoint coords;
u32 width;
u32 height;
};
struct AnalogVector
{
// GUI
wxSlider *slider;
wxSpinCtrl *spinner;
u8 val = 127;
bool renderRequired = false;
bool isControllerBypassed = false;
u8 prevVal = 127;
bool UpdateData(u8 &padDataVal, bool ignoreRealController, bool readOnly);
};
struct AnalogPosition
{
wxPoint centerCoords;
wxPoint endCoords;
int lineThickness;
int radius;
};
class VirtualPadElement
{
public:
virtual void UpdateGuiElement(std::queue<VirtualPadElement *> *renderQueue, bool &clearScreenRequired) = 0;
virtual void Render(wxDC &dc) = 0;
};
class ControllerNormalButton : VirtualPadElement
{
public:
/// GUI
ImageFile icon;
wxCheckBox *pressedBox;
bool pressed = false;
/// State
bool renderRequired = false;
bool isControllerBypassed;
bool prevPressedVal;
void UpdateGuiElement(std::queue<VirtualPadElement *> *renderQueue, bool &clearScreenRequired) override;
void Render(wxDC &dc) override;
bool UpdateData(bool &padDataVal, bool ignoreRealController, bool readOnly);
};
class ControllerPressureButton : VirtualPadElement
{
public:
/// GUI
ImageFile icon;
wxSpinCtrl *pressureSpinner;
bool pressed = false;
u8 pressure;
/// State Management
bool renderRequired = false;
bool isControllerPressBypassed;
bool isControllerPressureBypassed;
bool prevPressedVal;
u8 prevPressureVal;
void UpdateGuiElement(std::queue<VirtualPadElement *> *renderQueue, bool &clearScreenRequired) override;
void Render(wxDC &dc) override;
bool UpdateData(bool &padDataVal, bool ignoreRealController, bool readOnly);
bool UpdateData(u8 &padDataVal, bool ignoreRealController, bool readOnly);
};
class AnalogStick : VirtualPadElement
{
public:
AnalogVector xVector;
AnalogVector yVector;
AnalogPosition positionGraphic;
void UpdateGuiElement(std::queue<VirtualPadElement *> *renderQueue, bool &clearScreenRequired) override;
void Render(wxDC &dc) override;
};