diff --git a/pcsx2/Recording/RecordingInputManager.h b/pcsx2/Recording/RecordingInputManager.h deleted file mode 100644 index 42c7bf40ad..0000000000 --- a/pcsx2/Recording/RecordingInputManager.h +++ /dev/null @@ -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 . - */ - -#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 diff --git a/pcsx2/Recording/VirtualPad/VirtualPad.cpp b/pcsx2/Recording/VirtualPad/VirtualPad.cpp new file mode 100644 index 0000000000..e46460b3f3 --- /dev/null +++ b/pcsx2/Recording/VirtualPad/VirtualPad.cpp @@ -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 . + */ + +#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().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().Get(), wxPoint(968, 498)), this, wxPoint(1062, 660)); + InitPressureButtonGUIElements(virtualPadData.circle, NewBitmap(EmbeddedImage().Get(), wxPoint(1057, 413)), this, wxPoint(1062, 700)); + InitPressureButtonGUIElements(virtualPadData.triangle, NewBitmap(EmbeddedImage().Get(), wxPoint(968, 325)), this, wxPoint(1062, 740)); + InitPressureButtonGUIElements(virtualPadData.square, NewBitmap(EmbeddedImage().Get(), wxPoint(879, 413)), this, wxPoint(1062, 780)); + InitPressureButtonGUIElements(virtualPadData.down, NewBitmap(EmbeddedImage().Get(), wxPoint(191, 488)), this, wxPoint(199, 660), true); + InitPressureButtonGUIElements(virtualPadData.right, NewBitmap(EmbeddedImage().Get(), wxPoint(255, 429)), this, wxPoint(199, 700), true); + InitPressureButtonGUIElements(virtualPadData.up, NewBitmap(EmbeddedImage().Get(), wxPoint(191, 354)), this, wxPoint(199, 740), true); + InitPressureButtonGUIElements(virtualPadData.left, NewBitmap(EmbeddedImage().Get(), wxPoint(115, 429)), this, wxPoint(199, 780), true); + InitPressureButtonGUIElements(virtualPadData.l1, NewBitmap(EmbeddedImage().Get(), wxPoint(166, 8)), this, wxPoint(294, 20)); + InitPressureButtonGUIElements(virtualPadData.l2, NewBitmap(EmbeddedImage().Get(), wxPoint(166, 81)), this, wxPoint(294, 100)); + InitPressureButtonGUIElements(virtualPadData.r1, NewBitmap(EmbeddedImage().Get(), wxPoint(958, 7)), this, wxPoint(940, 20), true); + InitPressureButtonGUIElements(virtualPadData.r2, NewBitmap(EmbeddedImage().Get(), wxPoint(958, 81)), this, wxPoint(940, 100), true); + + InitNormalButtonGUIElements(virtualPadData.select, NewBitmap(EmbeddedImage().Get(), wxPoint(473, 441)), this, wxPoint(545, 448)); + InitNormalButtonGUIElements(virtualPadData.start, NewBitmap(EmbeddedImage().Get(), wxPoint(710, 440)), this, wxPoint(675, 448)); + InitNormalButtonGUIElements(virtualPadData.l3, NewBitmap(EmbeddedImage().Get(), wxPoint(347, 585)), this, wxPoint(440, 835)); + InitNormalButtonGUIElements(virtualPadData.r3, NewBitmap(EmbeddedImage().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; +} diff --git a/pcsx2/Recording/VirtualPad/VirtualPad.h b/pcsx2/Recording/VirtualPad/VirtualPad.h new file mode 100644 index 0000000000..71c4e4e622 --- /dev/null +++ b/pcsx2/Recording/VirtualPad/VirtualPad.h @@ -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 . + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 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 buttonElements; + std::map pressureElements; + std::map 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); +}; diff --git a/pcsx2/Recording/VirtualPad/VirtualPadData.cpp b/pcsx2/Recording/VirtualPad/VirtualPadData.cpp new file mode 100644 index 0000000000..b946275655 --- /dev/null +++ b/pcsx2/Recording/VirtualPad/VirtualPadData.cpp @@ -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 . + */ + +#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(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; +} \ No newline at end of file diff --git a/pcsx2/Recording/VirtualPad/VirtualPadData.h b/pcsx2/Recording/VirtualPad/VirtualPadData.h new file mode 100644 index 0000000000..a30fb3562b --- /dev/null +++ b/pcsx2/Recording/VirtualPad/VirtualPadData.h @@ -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 . +*/ + +#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); +}; diff --git a/pcsx2/Recording/VirtualPad/VirtualPadResources.cpp b/pcsx2/Recording/VirtualPad/VirtualPadResources.cpp new file mode 100644 index 0000000000..5dd8189884 --- /dev/null +++ b/pcsx2/Recording/VirtualPad/VirtualPadResources.cpp @@ -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 . +*/ + +#include "PrecompiledHeader.h" + +#include "Recording/VirtualPad/VirtualPadResources.h" + +void ControllerNormalButton::UpdateGuiElement(std::queue *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 *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 *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; +} \ No newline at end of file diff --git a/pcsx2/Recording/VirtualPad/VirtualPadResources.h b/pcsx2/Recording/VirtualPad/VirtualPadResources.h new file mode 100644 index 0000000000..9e3790b542 --- /dev/null +++ b/pcsx2/Recording/VirtualPad/VirtualPadResources.h @@ -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 . +*/ + +#pragma once + +#include + +#include +#include +#include +#include +#include + +#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 *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 *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 *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 *renderQueue, bool &clearScreenRequired) override; + void Render(wxDC &dc) override; +};