emu_window.cpp
1 // Copyright 2014 Citra Emulator Project 2 // Licensed under GPLv2 or any later version 3 // Refer to the license.txt file included. 4 5 #include <cmath> 6 #include <mutex> 7 #include "common/settings.h" 8 #include "core/3ds.h" 9 #include "core/frontend/emu_window.h" 10 #include "core/frontend/input.h" 11 12 namespace Frontend { 13 /// We need a global touch state that is shared across the different window instances 14 static std::weak_ptr<EmuWindow::TouchState> global_touch_state; 15 16 GraphicsContext::~GraphicsContext() = default; 17 18 class EmuWindow::TouchState : public Input::Factory<Input::TouchDevice>, 19 public std::enable_shared_from_this<TouchState> { 20 public: 21 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage&) override { 22 return std::make_unique<Device>(shared_from_this()); 23 } 24 25 std::mutex mutex; 26 27 bool touch_pressed = false; ///< True if touchpad area is currently pressed, otherwise false 28 29 float touch_x = 0.0f; ///< Touchpad X-position 30 float touch_y = 0.0f; ///< Touchpad Y-position 31 32 private: 33 class Device : public Input::TouchDevice { 34 public: 35 explicit Device(std::weak_ptr<TouchState>&& touch_state) : touch_state(touch_state) {} 36 std::tuple<float, float, bool> GetStatus() const override { 37 if (auto state = touch_state.lock()) { 38 std::scoped_lock guard{state->mutex}; 39 return std::make_tuple(state->touch_x, state->touch_y, state->touch_pressed); 40 } 41 return std::make_tuple(0.0f, 0.0f, false); 42 } 43 44 private: 45 std::weak_ptr<TouchState> touch_state; 46 }; 47 }; 48 49 EmuWindow::EmuWindow() { 50 CreateTouchState(); 51 }; 52 53 EmuWindow::EmuWindow(bool is_secondary_) : is_secondary{is_secondary_} { 54 CreateTouchState(); 55 } 56 57 EmuWindow::~EmuWindow() = default; 58 59 bool EmuWindow::IsWithinTouchscreen(const Layout::FramebufferLayout& layout, unsigned framebuffer_x, 60 unsigned framebuffer_y) { 61 #ifndef ANDROID 62 // If separate windows and the touch is in the primary (top) screen, ignore it. 63 if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows && 64 !is_secondary && !Settings::values.swap_screen.GetValue()) { 65 return false; 66 } 67 #endif 68 69 if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::SideBySide) { 70 return (framebuffer_y >= layout.bottom_screen.top && 71 framebuffer_y < layout.bottom_screen.bottom && 72 ((framebuffer_x >= layout.bottom_screen.left / 2 && 73 framebuffer_x < layout.bottom_screen.right / 2) || 74 (framebuffer_x >= (layout.bottom_screen.left / 2) + (layout.width / 2) && 75 framebuffer_x < (layout.bottom_screen.right / 2) + (layout.width / 2)))); 76 } else if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR) { 77 return (framebuffer_y >= layout.bottom_screen.top && 78 framebuffer_y < layout.bottom_screen.bottom && 79 ((framebuffer_x >= layout.bottom_screen.left && 80 framebuffer_x < layout.bottom_screen.right) || 81 (framebuffer_x >= layout.cardboard.bottom_screen_right_eye + (layout.width / 2) && 82 framebuffer_x < layout.cardboard.bottom_screen_right_eye + 83 layout.bottom_screen.GetWidth() + (layout.width / 2)))); 84 } else { 85 return (framebuffer_y >= layout.bottom_screen.top && 86 framebuffer_y < layout.bottom_screen.bottom && 87 framebuffer_x >= layout.bottom_screen.left && 88 framebuffer_x < layout.bottom_screen.right); 89 } 90 } 91 92 std::tuple<unsigned, unsigned> EmuWindow::ClipToTouchScreen(unsigned new_x, unsigned new_y) const { 93 if (new_x >= framebuffer_layout.width / 2) { 94 if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::SideBySide) 95 new_x -= framebuffer_layout.width / 2; 96 else if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR) 97 new_x -= 98 (framebuffer_layout.width / 2) - (framebuffer_layout.cardboard.user_x_shift * 2); 99 } 100 if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::SideBySide) { 101 new_x = std::max(new_x, framebuffer_layout.bottom_screen.left / 2); 102 new_x = std::min(new_x, framebuffer_layout.bottom_screen.right / 2 - 1); 103 } else { 104 new_x = std::max(new_x, framebuffer_layout.bottom_screen.left); 105 new_x = std::min(new_x, framebuffer_layout.bottom_screen.right - 1); 106 } 107 108 new_y = std::max(new_y, framebuffer_layout.bottom_screen.top); 109 new_y = std::min(new_y, framebuffer_layout.bottom_screen.bottom - 1); 110 111 return std::make_tuple(new_x, new_y); 112 } 113 114 void EmuWindow::CreateTouchState() { 115 touch_state = global_touch_state.lock(); 116 if (touch_state) { 117 return; 118 } 119 touch_state = std::make_shared<TouchState>(); 120 Input::RegisterFactory<Input::TouchDevice>("emu_window", touch_state); 121 global_touch_state = touch_state; 122 } 123 124 bool EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) { 125 if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) 126 return false; 127 128 if (framebuffer_x >= framebuffer_layout.width / 2) { 129 if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::SideBySide) 130 framebuffer_x -= framebuffer_layout.width / 2; 131 else if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR) 132 framebuffer_x -= 133 (framebuffer_layout.width / 2) - (framebuffer_layout.cardboard.user_x_shift * 2); 134 } 135 std::scoped_lock guard(touch_state->mutex); 136 if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::SideBySide) { 137 touch_state->touch_x = 138 static_cast<float>(framebuffer_x - framebuffer_layout.bottom_screen.left / 2) / 139 (framebuffer_layout.bottom_screen.right / 2 - 140 framebuffer_layout.bottom_screen.left / 2); 141 } else { 142 touch_state->touch_x = 143 static_cast<float>(framebuffer_x - framebuffer_layout.bottom_screen.left) / 144 (framebuffer_layout.bottom_screen.right - framebuffer_layout.bottom_screen.left); 145 } 146 touch_state->touch_y = 147 static_cast<float>(framebuffer_y - framebuffer_layout.bottom_screen.top) / 148 (framebuffer_layout.bottom_screen.bottom - framebuffer_layout.bottom_screen.top); 149 150 if (!framebuffer_layout.is_rotated) { 151 std::swap(touch_state->touch_x, touch_state->touch_y); 152 touch_state->touch_x = 1.f - touch_state->touch_x; 153 } 154 155 touch_state->touch_pressed = true; 156 return true; 157 } 158 159 void EmuWindow::TouchReleased() { 160 std::scoped_lock guard{touch_state->mutex}; 161 touch_state->touch_pressed = false; 162 touch_state->touch_x = 0; 163 touch_state->touch_y = 0; 164 } 165 166 void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) { 167 if (!touch_state->touch_pressed) 168 return; 169 170 if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) 171 std::tie(framebuffer_x, framebuffer_y) = ClipToTouchScreen(framebuffer_x, framebuffer_y); 172 173 TouchPressed(framebuffer_x, framebuffer_y); 174 } 175 176 void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_portrait_mode) { 177 Layout::FramebufferLayout layout; 178 179 // If in portrait mode, only the MobilePortrait option really makes sense 180 const Settings::LayoutOption layout_option = is_portrait_mode 181 ? Settings::LayoutOption::MobilePortrait 182 : Settings::values.layout_option.GetValue(); 183 const auto min_size = 184 Layout::GetMinimumSizeFromLayout(layout_option, Settings::values.upright_screen.GetValue()); 185 186 if (Settings::values.custom_layout.GetValue() == true) { 187 layout = Layout::CustomFrameLayout(width, height, Settings::values.swap_screen.GetValue()); 188 } else { 189 width = std::max(width, min_size.first); 190 height = std::max(height, min_size.second); 191 192 switch (layout_option) { 193 case Settings::LayoutOption::SingleScreen: 194 layout = 195 Layout::SingleFrameLayout(width, height, Settings::values.swap_screen.GetValue(), 196 Settings::values.upright_screen.GetValue()); 197 break; 198 case Settings::LayoutOption::LargeScreen: 199 layout = 200 Layout::LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(), 201 Settings::values.upright_screen.GetValue(), 202 Settings::values.large_screen_proportion.GetValue(), 203 Layout::VerticalAlignment::Bottom); 204 break; 205 case Settings::LayoutOption::HybridScreen: 206 layout = 207 Layout::HybridScreenLayout(width, height, Settings::values.swap_screen.GetValue(), 208 Settings::values.upright_screen.GetValue()); 209 break; 210 case Settings::LayoutOption::SideScreen: 211 layout = 212 Layout::LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(), 213 Settings::values.upright_screen.GetValue(), 1.0f, 214 Layout::VerticalAlignment::Bottom); 215 break; 216 #ifndef ANDROID 217 case Settings::LayoutOption::SeparateWindows: 218 layout = Layout::SeparateWindowsLayout(width, height, is_secondary, 219 Settings::values.upright_screen.GetValue()); 220 break; 221 #endif 222 case Settings::LayoutOption::MobilePortrait: 223 layout = Layout::MobilePortraitFrameLayout(width, height, 224 Settings::values.swap_screen.GetValue()); 225 break; 226 case Settings::LayoutOption::MobileLandscape: 227 layout = 228 Layout::LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(), 229 false, 2.25f, Layout::VerticalAlignment::Top); 230 break; 231 case Settings::LayoutOption::Default: 232 default: 233 layout = 234 Layout::DefaultFrameLayout(width, height, Settings::values.swap_screen.GetValue(), 235 Settings::values.upright_screen.GetValue()); 236 break; 237 } 238 UpdateMinimumWindowSize(min_size); 239 } 240 if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR) { 241 layout = Layout::GetCardboardSettings(layout); 242 } 243 NotifyFramebufferLayoutChanged(layout); 244 } 245 246 void EmuWindow::UpdateMinimumWindowSize(std::pair<unsigned, unsigned> min_size) { 247 WindowConfig new_config = config; 248 new_config.min_client_area_size = min_size; 249 SetConfig(new_config); 250 ProcessConfigurationChanges(); 251 } 252 253 } // namespace Frontend