/ src / core / frontend / emu_window.cpp
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