summaryrefslogtreecommitdiffstats
path: root/src/core/frontend/emu_window.h
blob: 835c4d50038f44d2906229f6018401804e626cb1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.

#pragma once

#include <tuple>
#include <utility>
#include "common/common_types.h"
#include "common/framebuffer_layout.h"
#include "common/math_util.h"
#include "core/hle/service/hid/hid.h"

/**
 * 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).
 * - 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.
 */
class EmuWindow {
public:
    /// Data structure to store emuwindow configuration
    struct WindowConfig {
        bool fullscreen;
        int res_width;
        int res_height;
        std::pair<unsigned, unsigned> min_client_area_size;
    };

    /// Swap buffers to display the next frame
    virtual void SwapBuffers() = 0;

    /// Polls window events
    virtual void PollEvents() = 0;

    /// 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;

    virtual void ReloadSetKeymaps() = 0;

    /**
     * Signals a button press action to the HID module.
     * @param pad_state indicates which button to press
     * @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad.
     */
    void ButtonPressed(Service::HID::PadState pad_state);

    /**
     * Signals a button release action to the HID module.
     * @param pad_state indicates which button to press
     * @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad.
     */
    void ButtonReleased(Service::HID::PadState pad_state);

    /**
     * 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);

    /**
     * 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
     */
    void TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y);

    /// Signal that a touch released event has occurred (e.g. mouse click released)
    void TouchReleased();

    /**
     * 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
     */
    void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y);

    /**
     * Gets the current pad state (which buttons are pressed).
     * @note This should be called by the core emu thread to get a state set by the window thread.
     * @note This doesn't include analog input like circle pad direction
     * @todo Fix this function to be thread-safe.
     * @return PadState object indicating the current pad state
     */
    Service::HID::PadState GetPadState() const {
        return pad_state;
    }

    /**
     * Gets the current circle pad state.
     * @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);
    }

    /**
     * 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
     */
    std::tuple<u16, u16, bool> GetTouchState() const {
        return std::make_tuple(touch_x, touch_y, touch_pressed);
    }

    /**
     * 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
    }

    /**
     * Returns currently active configuration.
     * @note Accesses to the returned object need not be consistent because it may be modified in
     * another thread
     */
    const WindowConfig& GetActiveConfig() const {
        return active_config;
    }

    /**
     * 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.
     */
    void SetConfig(const WindowConfig& val) {
        config = val;
    }

    /**
      * Gets the framebuffer layout (width, height, and screen regions)
      * @note This method is thread-safe
      */
    const Layout::FramebufferLayout& GetFramebufferLayout() const {
        return framebuffer_layout;
    }

    /**
     * Convenience method to update the current frame layout
     * Read from the current settings to determine which layout to use.
     */
    void UpdateCurrentFramebufferLayout(unsigned width, unsigned height);

protected:
    EmuWindow() {
        // TODO: Find a better place to set this.
        config.min_client_area_size = std::make_pair(400u, 480u);
        active_config = config;
        pad_state.hex = 0;
        touch_x = 0;
        touch_y = 0;
        circle_pad_x = 0;
        circle_pad_y = 0;
        touch_pressed = false;
    }
    virtual ~EmuWindow() {}

    /**
     * Processes any pending configuration changes from the last SetConfig call.
     * This method invokes OnMinimalClientAreaChangeRequest if the corresponding configuration
     * field changed.
     * @note Implementations will usually want to call this from the GUI thread.
     * @todo Actually call this in existing implementations.
     */
    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;
        }
    }

    /**
     * Update framebuffer layout with the given parameter.
     * @note EmuWindow implementations will usually use this in window resize event handlers.
     */
    void NotifyFramebufferLayoutChanged(const Layout::FramebufferLayout& layout) {
        framebuffer_layout = layout;
    }

    /**
     * Update internal client area size with the given parameter.
     * @note EmuWindow implementations will usually use this in window resize event handlers.
     */
    void NotifyClientAreaSizeChanged(const std::pair<unsigned, unsigned>& size) {
        client_area_width = size.first;
        client_area_height = size.second;
    }

private:
    /**
     * 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.
     */
    virtual void OnMinimalClientAreaChangeRequest(
        const std::pair<unsigned, unsigned>& minimal_size) {
        // By default, ignore this request and do nothing.
    }

    Layout::FramebufferLayout framebuffer_layout; ///< Current framebuffer layout

    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.

    WindowConfig config;        ///< Internal configuration (changes pending for being applied in
                                /// ProcessConfigurationChanges)
    WindowConfig active_config; ///< Internal active configuration

    bool touch_pressed; ///< True if touchpad area is currently pressed, otherwise false

    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)

    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)

    /**
     * Clip the provided coordinates to be inside the touchscreen area.
     */
    std::tuple<unsigned, unsigned> ClipToTouchScreen(unsigned new_x, unsigned new_y);

    Service::HID::PadState pad_state;
};