2014-04-08 23:18:23 -04:00
// Copyright 2014 Citra Emulator Project
2014-12-16 21:38:14 -08:00
// Licensed under GPLv2 or any later version
2014-04-08 23:18:23 -04:00
// Refer to the license.txt file included.
2013-09-04 21:00:29 -04:00
2014-04-08 23:18:23 -04:00
# pragma once
2013-09-04 21:00:29 -04:00
2015-06-21 14:02:11 +01:00
# include <tuple>
# include <utility>
2015-05-06 04:06:12 -03:00
# include "common/common_types.h"
2015-03-07 17:21:19 -05:00
# include "common/math_util.h"
2015-06-21 14:02:11 +01:00
# include "core/hle/service/hid/hid.h"
2014-11-13 18:12:27 +01:00
/**
* Abstraction class used to provide an interface between emulation code and the frontend
* (e.g. SDL, QGLWidget, GLFW, etc...).
*
* Design notes on the interaction between EmuWindow and the emulation core:
* - Generally, decisions on anything visible to the user should be left up to the GUI.
* For example, the emulation core should not try to dictate some window title or size.
* This stuff is not the core's business and only causes problems with regards to thread-safety
* anyway.
* - Under certain circumstances, it may be desirable for the core to politely request the GUI
* to set e.g. a minimum window size. However, the GUI should always be free to ignore any
* such hints.
* - EmuWindow may expose some of its state as read-only to the emulation core, however care
* should be taken to make sure the provided information is self-consistent. This requires
* some sort of synchronization (most of this is still a TODO).
2014-11-13 20:45:37 +01:00
* - DO NOT TREAT THIS CLASS AS A GUI TOOLKIT ABSTRACTION LAYER. That's not what it is. Please
* re-read the upper points again and think about it if you don't see this.
2014-11-13 18:12:27 +01:00
*/
2013-09-04 21:00:29 -04:00
class EmuWindow
{
public :
2014-11-13 18:12:27 +01:00
/// Data structure to store emuwindow configuration
2014-09-12 17:06:13 -07:00
struct WindowConfig {
2013-09-04 21:00:29 -04:00
bool fullscreen ;
int res_width ;
int res_height ;
2014-10-12 22:46:33 +02:00
std : : pair < unsigned , unsigned > min_client_area_size ;
2013-09-04 21:00:29 -04:00
} ;
2015-03-07 17:21:19 -05:00
/// Describes the layout of the window framebuffer (size and top/bottom screen positions)
struct FramebufferLayout {
/**
* Factory method for constructing a default FramebufferLayout
* @param width Window framebuffer width in pixels
* @param height Window framebuffer height in pixels
* @return Newly created FramebufferLayout object with default screen regions initialized
*/
2015-03-07 18:26:28 -05:00
static FramebufferLayout DefaultScreenLayout ( unsigned width , unsigned height ) ;
2015-03-07 17:21:19 -05:00
unsigned width ;
unsigned height ;
MathUtil : : Rectangle < unsigned > top_screen ;
MathUtil : : Rectangle < unsigned > bottom_screen ;
} ;
2013-09-04 21:00:29 -04:00
/// Swap buffers to display the next frame
virtual void SwapBuffers ( ) = 0 ;
2014-04-01 18:20:08 -04:00
/// Polls window events
virtual void PollEvents ( ) = 0 ;
2013-09-04 21:00:29 -04:00
/// Makes the graphics context current for the caller thread
virtual void MakeCurrent ( ) = 0 ;
/// Releases (dunno if this is the "right" word) the GLFW context from the caller thread
virtual void DoneCurrent ( ) = 0 ;
2014-09-12 17:06:13 -07:00
virtual void ReloadSetKeymaps ( ) = 0 ;
2016-05-12 13:09:36 +03:00
/**
* Signals a button press action to the HID module.
* @param pad_state indicates which button to press
2016-05-15 19:35:06 +03:00
* @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad.
2016-05-12 13:09:36 +03:00
*/
void ButtonPressed ( Service : : HID : : PadState pad_state ) ;
/**
* Signals a button release action to the HID module.
* @param pad_state indicates which button to press
2016-05-15 19:35:06 +03:00
* @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad.
2016-05-12 13:09:36 +03:00
*/
void ButtonReleased ( Service : : HID : : PadState pad_state ) ;
2014-09-03 18:12:58 -07:00
2016-05-12 13:09:36 +03:00
/**
* Signals a circle pad change action to the HID module.
* @param x new x-coordinate of the circle pad, in the range [-1.0, 1.0]
* @param y new y-coordinate of the circle pad, in the range [-1.0, 1.0]
* @note the coordinates will be normalized if the radius is larger than 1
*/
void CirclePadUpdated ( float x , float y ) ;
2014-09-03 18:12:58 -07:00
2015-03-08 03:13:26 -04:00
/**
* Signal that a touch pressed event has occurred (e.g. mouse click pressed)
* @param framebuffer_x Framebuffer x-coordinate that was pressed
* @param framebuffer_y Framebuffer y-coordinate that was pressed
*/
2015-03-09 00:14:59 -04:00
void TouchPressed ( unsigned framebuffer_x , unsigned framebuffer_y ) ;
2015-03-08 03:13:26 -04:00
2015-03-09 00:14:59 -04:00
/// Signal that a touch released event has occurred (e.g. mouse click released)
void TouchReleased ( ) ;
2015-03-08 03:13:26 -04:00
/**
* Signal that a touch movement event has occurred (e.g. mouse was moved over the emu window)
* @param framebuffer_x Framebuffer x-coordinate
* @param framebuffer_y Framebuffer y-coordinate
*/
2015-03-09 00:14:59 -04:00
void TouchMoved ( unsigned framebuffer_x , unsigned framebuffer_y ) ;
/**
2016-05-12 13:09:36 +03:00
* Gets the current pad state (which buttons are pressed).
2015-03-09 00:14:59 -04:00
* @note This should be called by the core emu thread to get a state set by the window thread.
2016-05-12 13:09:36 +03:00
* @note This doesn't include analog input like circle pad direction
2015-03-09 00:14:59 -04:00
* @todo Fix this function to be thread-safe.
* @return PadState object indicating the current pad state
*/
2016-03-26 10:46:48 +03:00
Service : : HID : : PadState GetPadState ( ) const {
2015-03-09 00:14:59 -04:00
return pad_state ;
}
2016-05-12 13:09:36 +03:00
/**
2016-05-15 19:35:06 +03:00
* Gets the current circle pad state.
2016-05-12 13:09:36 +03:00
* @note This should be called by the core emu thread to get a state set by the window thread.
* @todo Fix this function to be thread-safe.
* @return std::tuple of (x, y), where `x` and `y` are the circle pad coordinates
*/
std : : tuple < s16 , s16 > GetCirclePadState ( ) const {
return std : : make_tuple ( circle_pad_x , circle_pad_y ) ;
}
2015-03-09 00:14:59 -04:00
/**
* Gets the current touch screen state (touch X/Y coordinates and whether or not it is pressed).
* @note This should be called by the core emu thread to get a state set by the window thread.
* @todo Fix this function to be thread-safe.
* @return std::tuple of (x, y, pressed) where `x` and `y` are the touch coordinates and
* `pressed` is true if the touch screen is currently being pressed
*/
2016-03-26 10:46:48 +03:00
std : : tuple < u16 , u16 , bool > GetTouchState ( ) const {
2015-03-09 00:14:59 -04:00
return std : : make_tuple ( touch_x , touch_y , touch_pressed ) ;
}
2015-03-08 03:13:26 -04:00
2016-03-18 22:27:36 +02:00
/**
* Gets the current accelerometer state (acceleration along each three axis).
* Axis explained:
* +x is the same direction as LEFT on D-pad.
* +y is normal to the touch screen, pointing outward.
* +z is the same direction as UP on D-pad.
* Units:
* 1 unit of return value = 1/512 g (measured by hw test),
* where g is the gravitational acceleration (9.8 m/sec2).
* @note This should be called by the core emu thread to get a state set by the window thread.
* @todo Implement accelerometer input in front-end.
* @return std::tuple of (x, y, z)
*/
std : : tuple < s16 , s16 , s16 > GetAccelerometerState ( ) const {
// stubbed
return std : : make_tuple ( 0 , - 512 , 0 ) ;
}
/**
* Gets the current gyroscope state (angular rates about each three axis).
* Axis explained:
* +x is the same direction as LEFT on D-pad.
* +y is normal to the touch screen, pointing outward.
* +z is the same direction as UP on D-pad.
* Orientation is determined by right-hand rule.
* Units:
* 1 unit of return value = (1/coef) deg/sec,
* where coef is the return value of GetGyroscopeRawToDpsCoefficient().
* @note This should be called by the core emu thread to get a state set by the window thread.
* @todo Implement gyroscope input in front-end.
* @return std::tuple of (x, y, z)
*/
std : : tuple < s16 , s16 , s16 > GetGyroscopeState ( ) const {
// stubbed
return std : : make_tuple ( 0 , 0 , 0 ) ;
}
/**
* Gets the coefficient for units conversion of gyroscope state.
* The conversion formula is r = coefficient * v,
* where v is angular rate in deg/sec,
* and r is the gyroscope state.
* @return float-type coefficient
*/
f32 GetGyroscopeRawToDpsCoefficient ( ) const {
return 14.375f ; // taken from hw test, and gyroscope's document
}
2014-11-13 18:12:27 +01:00
/**
* Returns currently active configuration.
* @note Accesses to the returned object need not be consistent because it may be modified in another thread
*/
2014-10-12 22:46:33 +02:00
const WindowConfig & GetActiveConfig ( ) const {
return active_config ;
2014-04-08 23:18:23 -04:00
}
2014-11-13 18:12:27 +01:00
/**
* Requests the internal configuration to be replaced by the specified argument at some point in the future.
* @note This method is thread-safe, because it delays configuration changes to the GUI event loop. Hence there is no guarantee on when the requested configuration will be active.
*/
2014-09-12 17:06:13 -07:00
void SetConfig ( const WindowConfig & val ) {
2014-10-12 18:14:57 +02:00
config = val ;
2014-04-08 23:18:23 -04:00
}
2014-08-29 22:23:12 -07:00
2014-10-12 18:14:57 +02:00
/**
2015-03-07 17:21:19 -05:00
* Gets the framebuffer layout (width, height, and screen regions)
2014-11-13 18:12:27 +01:00
* @note This method is thread-safe
2014-10-12 18:14:57 +02:00
*/
2015-03-07 17:21:19 -05:00
const FramebufferLayout & GetFramebufferLayout ( ) const {
return framebuffer_layout ;
2014-04-08 23:18:23 -04:00
}
2013-09-04 21:00:29 -04:00
protected :
2015-03-09 00:14:59 -04:00
EmuWindow ( ) {
2014-11-13 20:45:37 +01:00
// TODO: Find a better place to set this.
config . min_client_area_size = std : : make_pair ( 400u , 480u ) ;
2014-10-12 22:46:33 +02:00
active_config = config ;
2015-03-09 00:14:59 -04:00
pad_state . hex = 0 ;
touch_x = 0 ;
touch_y = 0 ;
2016-05-12 13:09:36 +03:00
circle_pad_x = 0 ;
circle_pad_y = 0 ;
2015-03-09 00:14:59 -04:00
touch_pressed = false ;
2014-10-12 22:46:33 +02:00
}
2013-09-04 21:00:29 -04:00
virtual ~ EmuWindow ( ) { }
2014-11-13 18:12:27 +01:00
/**
* Processes any pending configuration changes from the last SetConfig call.
2014-11-13 18:24:15 +01:00
* This method invokes OnMinimalClientAreaChangeRequest if the corresponding configuration
* field changed.
2014-11-13 18:12:27 +01:00
* @note Implementations will usually want to call this from the GUI thread.
2014-11-13 20:31:34 +01:00
* @todo Actually call this in existing implementations.
2014-11-13 18:12:27 +01:00
*/
void ProcessConfigurationChanges ( ) {
// TODO: For proper thread safety, we should eventually implement a proper
// multiple-writer/single-reader queue...
if ( config . min_client_area_size ! = active_config . min_client_area_size ) {
OnMinimalClientAreaChangeRequest ( config . min_client_area_size ) ;
config . min_client_area_size = active_config . min_client_area_size ;
}
}
/**
2015-03-07 17:21:19 -05:00
* Update framebuffer layout with the given parameter.
2014-11-13 18:12:27 +01:00
* @note EmuWindow implementations will usually use this in window resize event handlers.
*/
2015-03-07 17:21:19 -05:00
void NotifyFramebufferLayoutChanged ( const FramebufferLayout & layout ) {
framebuffer_layout = layout ;
2014-10-12 18:14:57 +02:00
}
2013-09-04 21:00:29 -04:00
2014-11-13 18:12:27 +01:00
/**
* Update internal client area size with the given parameter.
* @note EmuWindow implementations will usually use this in window resize event handlers.
*/
2014-10-12 22:46:33 +02:00
void NotifyClientAreaSizeChanged ( const std : : pair < unsigned , unsigned > & size ) {
2014-10-12 18:14:57 +02:00
client_area_width = size . first ;
client_area_height = size . second ;
}
2013-09-04 21:00:29 -04:00
private :
2014-11-13 18:24:15 +01:00
/**
* Handler called when the minimal client area was requested to be changed via SetConfig.
* For the request to be honored, EmuWindow implementations will usually reimplement this function.
*/
2014-10-12 22:46:33 +02:00
virtual void OnMinimalClientAreaChangeRequest ( const std : : pair < unsigned , unsigned > & minimal_size ) {
2014-11-13 18:24:15 +01:00
// By default, ignore this request and do nothing.
2014-10-12 22:46:33 +02:00
}
2015-03-07 17:21:19 -05:00
FramebufferLayout framebuffer_layout ; ///< Current framebuffer layout
2014-10-12 18:14:57 +02:00
unsigned client_area_width ; ///< Current client width, should be set by window impl.
unsigned client_area_height ; ///< Current client height, should be set by window impl.
2013-09-04 21:00:29 -04:00
2014-10-12 22:46:33 +02:00
WindowConfig config ; ///< Internal configuration (changes pending for being applied in ProcessConfigurationChanges)
WindowConfig active_config ; ///< Internal active configuration
2015-03-08 03:13:26 -04:00
2015-03-08 21:45:45 -04:00
bool touch_pressed ; ///< True if touchpad area is currently pressed, otherwise false
2015-03-09 00:14:59 -04:00
u16 touch_x ; ///< Touchpad X-position in native 3DS pixel coordinates (0-320)
u16 touch_y ; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240)
2016-05-12 13:09:36 +03:00
s16 circle_pad_x ; ///< Circle pad X-position in native 3DS pixel coordinates (-156 - 156)
s16 circle_pad_y ; ///< Circle pad Y-position in native 3DS pixel coordinates (-156 - 156)
2015-04-14 00:06:44 -04:00
/**
* Clip the provided coordinates to be inside the touchscreen area.
*/
std : : tuple < unsigned , unsigned > ClipToTouchScreen ( unsigned new_x , unsigned new_y ) ;
2015-03-09 00:14:59 -04:00
Service : : HID : : PadState pad_state ;
2013-09-04 21:00:29 -04:00
} ;