/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2022 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #ifndef XENIA_APP_EMULATOR_WINDOW_H_ #define XENIA_APP_EMULATOR_WINDOW_H_ #include #include #include "xenia/emulator.h" #include "xenia/gpu/command_processor.h" #include "xenia/ui/imgui_dialog.h" #include "xenia/ui/imgui_drawer.h" #include "xenia/ui/immediate_drawer.h" #include "xenia/ui/menu_item.h" #include "xenia/ui/presenter.h" #include "xenia/ui/window.h" #include "xenia/ui/window_listener.h" #include "xenia/ui/windowed_app_context.h" #include "xenia/xbox.h" #define MAX_USERS 4 namespace xe { namespace app { struct RecentTitleEntry { std::string title_name; std::filesystem::path path_to_file; std::time_t last_run_time; }; class EmulatorWindow { public: using steady_clock = std::chrono::steady_clock; // stdlib steady clock enum : size_t { // The UI is on top of the game and is open in special cases, so // lowest-priority. kZOrderHidInput, kZOrderImGui, kZOrderProfiler, // Emulator window controls are expected to be always accessible by the // user, so highest-priority. kZOrderEmulatorWindowInput, }; virtual ~EmulatorWindow(); static std::unique_ptr Create( Emulator* emulator, ui::WindowedAppContext& app_context, uint32_t width, uint32_t height); std::unique_ptr Gamepad_HotKeys_Listener; int32_t selected_title_index = -1; static constexpr int64_t diff_in_ms( const steady_clock::time_point t1, const steady_clock::time_point t2) noexcept { using ms = std::chrono::milliseconds; return std::chrono::duration_cast(t1 - t2).count(); } steady_clock::time_point last_mouse_up = steady_clock::now(); steady_clock::time_point last_mouse_down = steady_clock::now(); Emulator* emulator() const { return emulator_; } ui::WindowedAppContext& app_context() const { return app_context_; } ui::Window* window() const { return window_.get(); } ui::ImGuiDrawer* imgui_drawer() const { return imgui_drawer_.get(); } ui::Presenter* GetGraphicsSystemPresenter() const; void SetupGraphicsSystemPresenterPainting(); void ShutdownGraphicsSystemPresenterPainting(); void OnEmulatorInitialized(); xe::X_STATUS RunTitle(std::filesystem::path path_to_file); void UpdateTitle(); void SetFullscreen(bool fullscreen); void ToggleFullscreen(); void SetInitializingShaderStorage(bool initializing); void TakeScreenshot(); void ExportScreenshot(const xe::ui::RawImage& image); void SaveImage(const std::filesystem::path& path, const xe::ui::RawImage& image); // Types of button functions for hotkeys. enum class ButtonFunctions { ToggleFullscreen, RunTitle, CpuTimeScalarSetHalf, CpuTimeScalarSetDouble, CpuTimeScalarReset, ClearGPUCache, ToggleControllerVibration, ClearMemoryPageState, ReadbackResolve, ToggleLogging, IncTitleSelect, DecTitleSelect, Unknown }; enum class gpu_cvar { ClearMemoryPageState, ReadbackResolve, }; class ControllerHotKey { public: // If true the hotkey can be activated while a title is running, otherwise // false. bool title_passthru; // If true vibrate the controller after activating the hotkey, otherwise // false. bool rumble; std::string pretty; ButtonFunctions function; ControllerHotKey(ButtonFunctions fn = ButtonFunctions::Unknown, std::string pretty = "", bool rumble = false, bool active = true) { function = fn; this->pretty = pretty; title_passthru = active; this->rumble = rumble; } }; private: class EmulatorWindowListener final : public ui::WindowListener, public ui::WindowInputListener { public: explicit EmulatorWindowListener(EmulatorWindow& emulator_window) : emulator_window_(emulator_window) {} void OnClosing(ui::UIEvent& e) override; void OnFileDrop(ui::FileDropEvent& e) override; void OnKeyDown(ui::KeyEvent& e) override; void OnMouseDown(ui::MouseEvent& e) override; void OnMouseUp(ui::MouseEvent& e) override; private: EmulatorWindow& emulator_window_; }; class DisplayConfigGameConfigLoadCallback : public Emulator::GameConfigLoadCallback { public: DisplayConfigGameConfigLoadCallback(Emulator& emulator, EmulatorWindow& emulator_window) : Emulator::GameConfigLoadCallback(emulator), emulator_window_(emulator_window) {} void PostGameConfigLoad() override; private: EmulatorWindow& emulator_window_; }; class DisplayConfigDialog final : public ui::ImGuiDialog { public: DisplayConfigDialog(ui::ImGuiDrawer* imgui_drawer, EmulatorWindow& emulator_window) : ui::ImGuiDialog(imgui_drawer), emulator_window_(emulator_window) {} protected: void OnDraw(ImGuiIO& io) override; private: EmulatorWindow& emulator_window_; }; explicit EmulatorWindow(Emulator* emulator, ui::WindowedAppContext& app_context, uint32_t width, uint32_t height); bool Initialize(); // For comparisons, use GetSwapPostEffectForCvarValue instead as the default // fallback may be used for multiple values. static const char* GetCvarValueForSwapPostEffect( gpu::CommandProcessor::SwapPostEffect effect); static gpu::CommandProcessor::SwapPostEffect GetSwapPostEffectForCvarValue( const std::string& cvar_value); // For comparisons, use GetGuestOutputPaintEffectForCvarValue instead as the // default fallback may be used for multiple values. static const char* GetCvarValueForGuestOutputPaintEffect( ui::Presenter::GuestOutputPaintConfig::Effect effect); static ui::Presenter::GuestOutputPaintConfig::Effect GetGuestOutputPaintEffectForCvarValue(const std::string& cvar_value); static ui::Presenter::GuestOutputPaintConfig GetGuestOutputPaintConfigForCvars(); void ApplyDisplayConfigForCvars(); void OnKeyDown(ui::KeyEvent& e); void OnMouseDown(const ui::MouseEvent& e); void ToggleFullscreenOnDoubleClick(); void FileDrop(const std::filesystem::path& filename); void OnMouseUp(const ui::MouseEvent& e); void FileOpen(); void FileClose(); void InstallContent(); void ExtractZarchive(); void CreateZarchive(); void ShowContentDirectory(); void CpuTimeScalarReset(); void CpuTimeScalarSetHalf(); void CpuTimeScalarSetDouble(); void CpuBreakIntoDebugger(); void CpuBreakIntoHostDebugger(); void GpuTraceFrame(); void GpuClearCaches(); void ToggleDisplayConfigDialog(); void ToggleControllerVibration(); void ShowCompatibility(); void ShowFAQ(); void ShowBuildCommit(); EmulatorWindow::ControllerHotKey ProcessControllerHotkey(int buttons); void VibrateController(xe::hid::InputSystem* input_sys, uint32_t user_index, bool vibrate = true); void GamepadHotKeys(); void ToggleGPUSetting(gpu_cvar index); bool IsUseNexusForGameBarEnabled(); void DisplayHotKeysConfig(); static std::string CanonicalizeFileExtension( const std::filesystem::path& path); void RunPreviouslyPlayedTitle(); void FillRecentlyLaunchedTitlesMenu(xe::ui::MenuItem* recent_menu); void LoadRecentlyLaunchedTitles(); void AddRecentlyLaunchedTitle(std::filesystem::path path_to_file, std::string title_name); Emulator* emulator_; ui::WindowedAppContext& app_context_; EmulatorWindowListener window_listener_; std::unique_ptr window_; std::unique_ptr imgui_drawer_; std::unique_ptr display_config_game_config_load_callback_; // Creation may fail, in this case immediate drawer UI must not be drawn. std::unique_ptr immediate_drawer_; bool emulator_initialized_ = false; std::string base_title_; bool initializing_shader_storage_ = false; std::unique_ptr display_config_dialog_; std::vector recently_launched_titles_; }; } // namespace app } // namespace xe #endif // XENIA_APP_EMULATOR_WINDOW_H_