|
|
@@ -0,0 +1,971 @@
|
|
|
+/**
|
|
|
+ * PANDA 3D SOFTWARE
|
|
|
+ * Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
|
+ *
|
|
|
+ * All use of this software is subject to the terms of the revised BSD
|
|
|
+ * license. You should have received a copy of this license along
|
|
|
+ * with this source code in a file named "LICENSE."
|
|
|
+ *
|
|
|
+ * @file webGLGraphicsWindow.cxx
|
|
|
+ * @author rdb
|
|
|
+ * @date 2015-03-31
|
|
|
+ */
|
|
|
+
|
|
|
+#include "webGLGraphicsWindow.h"
|
|
|
+#include "webGLGraphicsStateGuardian.h"
|
|
|
+#include "config_webgldisplay.h"
|
|
|
+#include "mouseButton.h"
|
|
|
+#include "keyboardButton.h"
|
|
|
+#include "throw_event.h"
|
|
|
+#include "pointerData.h"
|
|
|
+#include <emscripten.h>
|
|
|
+
|
|
|
+TypeHandle WebGLGraphicsWindow::_type_handle;
|
|
|
+
|
|
|
+/**
|
|
|
+ *
|
|
|
+ */
|
|
|
+WebGLGraphicsWindow::
|
|
|
+WebGLGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
|
|
|
+ const std::string &name,
|
|
|
+ const FrameBufferProperties &fb_prop,
|
|
|
+ const WindowProperties &win_prop,
|
|
|
+ int flags,
|
|
|
+ GraphicsStateGuardian *gsg,
|
|
|
+ GraphicsOutput *host) :
|
|
|
+ GraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host)
|
|
|
+{
|
|
|
+ PT(GraphicsWindowInputDevice) device =
|
|
|
+ GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse");
|
|
|
+ add_input_device(device);
|
|
|
+ _input = device.p();
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ *
|
|
|
+ */
|
|
|
+WebGLGraphicsWindow::
|
|
|
+~WebGLGraphicsWindow() {
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Forces the pointer to the indicated position within the window, if
|
|
|
+ * possible.
|
|
|
+ *
|
|
|
+ * Returns true if successful, false on failure. This may fail if the mouse
|
|
|
+ * is not currently within the window, or if the API doesn't support this
|
|
|
+ * operation.
|
|
|
+ */
|
|
|
+bool WebGLGraphicsWindow::
|
|
|
+move_pointer(int device, int x, int y) {
|
|
|
+ if (device == 0 && _properties.get_mouse_mode() == WindowProperties::M_relative) {
|
|
|
+ // The pointer position is meaningless in relative mode, so we silently
|
|
|
+ // pretend that this worked.
|
|
|
+ _input->set_pointer_in_window(x, y);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * This function will be called within the draw thread before beginning
|
|
|
+ * rendering for a given frame. It should do whatever setup is required, and
|
|
|
+ * return true if the frame should be rendered, or false if it should be
|
|
|
+ * skipped.
|
|
|
+ */
|
|
|
+bool WebGLGraphicsWindow::
|
|
|
+begin_frame(FrameMode mode, Thread *current_thread) {
|
|
|
+ PStatTimer timer(_make_current_pcollector, current_thread);
|
|
|
+
|
|
|
+ begin_frame_spam(mode);
|
|
|
+ if (_gsg == nullptr) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ WebGLGraphicsStateGuardian *webgl_gsg;
|
|
|
+ DCAST_INTO_R(webgl_gsg, _gsg, false);
|
|
|
+
|
|
|
+ if (emscripten_is_webgl_context_lost(webgl_gsg->_context)) {
|
|
|
+ // The context was lost, and any GL calls we make will fail.
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (emscripten_webgl_make_context_current(webgl_gsg->_context) != EMSCRIPTEN_RESULT_SUCCESS) {
|
|
|
+ webgldisplay_cat.error()
|
|
|
+ << "Failed to make context current.\n";
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (mode == FM_render) {
|
|
|
+ // begin_render_texture();
|
|
|
+ clear_cube_map_selection();
|
|
|
+ }
|
|
|
+
|
|
|
+ _gsg->set_current_properties(&get_fb_properties());
|
|
|
+ return _gsg->begin_frame(current_thread);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * This function will be called within the draw thread after rendering is
|
|
|
+ * completed for a given frame. It should do whatever finalization is
|
|
|
+ * required.
|
|
|
+ */
|
|
|
+void WebGLGraphicsWindow::
|
|
|
+end_frame(FrameMode mode, Thread *current_thread) {
|
|
|
+ end_frame_spam(mode);
|
|
|
+ nassertv(_gsg != nullptr);
|
|
|
+
|
|
|
+ if (mode == FM_render) {
|
|
|
+ // end_render_texture();
|
|
|
+ copy_to_textures();
|
|
|
+ }
|
|
|
+
|
|
|
+ _gsg->end_frame(current_thread);
|
|
|
+
|
|
|
+ if (mode == FM_render) {
|
|
|
+ trigger_flip();
|
|
|
+ clear_cube_map_selection();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * This function will be called within the draw thread after begin_flip() has
|
|
|
+ * been called on all windows, to finish the exchange of the front and back
|
|
|
+ * buffers.
|
|
|
+ *
|
|
|
+ * This should cause the window to wait for the flip, if necessary.
|
|
|
+ */
|
|
|
+void WebGLGraphicsWindow::
|
|
|
+end_flip() {
|
|
|
+ GraphicsWindow::end_flip();
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Do whatever processing is necessary to ensure that the window responds to
|
|
|
+ * user events. Also, honor any requests recently made via
|
|
|
+ * request_properties()
|
|
|
+ *
|
|
|
+ * This function is called only within the window thread.
|
|
|
+ */
|
|
|
+void WebGLGraphicsWindow::
|
|
|
+process_events() {
|
|
|
+ GraphicsWindow::process_events();
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Applies the requested set of properties to the window, if possible, for
|
|
|
+ * instance to request a change in size or minimization status.
|
|
|
+ *
|
|
|
+ * The window properties are applied immediately, rather than waiting until
|
|
|
+ * the next frame. This implies that this method may *only* be called from
|
|
|
+ * within the window thread.
|
|
|
+ *
|
|
|
+ * The return value is true if the properties are set, false if they are
|
|
|
+ * ignored. This is mainly useful for derived classes to implement extensions
|
|
|
+ * to this function.
|
|
|
+ */
|
|
|
+void WebGLGraphicsWindow::
|
|
|
+set_properties_now(WindowProperties &properties) {
|
|
|
+ GraphicsWindow::set_properties_now(properties);
|
|
|
+
|
|
|
+ const char *target = "#canvas";
|
|
|
+
|
|
|
+ if (properties.has_size()) {
|
|
|
+ emscripten_set_canvas_element_size(target, properties.get_x_size(), properties.get_y_size());
|
|
|
+ _properties.set_size(properties.get_size());
|
|
|
+ properties.clear_size();
|
|
|
+ set_size_and_recalc(_properties.get_x_size(), _properties.get_y_size());
|
|
|
+ throw_event(get_window_event(), this);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (properties.has_fullscreen() &&
|
|
|
+ properties.get_fullscreen() != _properties.get_fullscreen()) {
|
|
|
+
|
|
|
+ if (properties.get_fullscreen()) {
|
|
|
+ EMSCRIPTEN_RESULT result = emscripten_request_fullscreen(target, true);
|
|
|
+
|
|
|
+ if (result == EMSCRIPTEN_RESULT_SUCCESS) {
|
|
|
+ _properties.set_fullscreen(true);
|
|
|
+ properties.clear_fullscreen();
|
|
|
+
|
|
|
+ } else if (result == EMSCRIPTEN_RESULT_DEFERRED) {
|
|
|
+ // We can't switch to fullscreen just yet - this action is deferred
|
|
|
+ // until we're in an event handler. We can't know for sure yet that
|
|
|
+ // fullscreen will be supported, but we shouldn't report failure.
|
|
|
+ properties.clear_fullscreen();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (emscripten_exit_fullscreen() == EMSCRIPTEN_RESULT_SUCCESS) {
|
|
|
+ _properties.set_fullscreen(false);
|
|
|
+ properties.clear_fullscreen();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (properties.has_mouse_mode() &&
|
|
|
+ properties.get_mouse_mode() != _properties.get_mouse_mode()) {
|
|
|
+
|
|
|
+ if (properties.get_mouse_mode() == WindowProperties::M_relative) {
|
|
|
+ EMSCRIPTEN_RESULT result = emscripten_request_pointerlock(target, true);
|
|
|
+
|
|
|
+ if (result == EMSCRIPTEN_RESULT_SUCCESS) {
|
|
|
+ // Great, we're in pointerlock mode.
|
|
|
+ _properties.set_mouse_mode(WindowProperties::M_absolute);
|
|
|
+ _properties.set_cursor_hidden(true);
|
|
|
+ properties.clear_mouse_mode();
|
|
|
+
|
|
|
+ } else if (result == EMSCRIPTEN_RESULT_DEFERRED) {
|
|
|
+ // We can't switch to pointerlock just yet - this action is deferred
|
|
|
+ // until we're in an event handler. We can't know for sure yet that
|
|
|
+ // pointerlock will be supported, but we shouldn't report failure.
|
|
|
+ properties.clear_mouse_mode();
|
|
|
+ if (properties.has_cursor_hidden() && properties.get_cursor_hidden()) {
|
|
|
+ properties.clear_cursor_hidden();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (emscripten_exit_pointerlock() == EMSCRIPTEN_RESULT_SUCCESS) {
|
|
|
+ _properties.set_mouse_mode(WindowProperties::M_absolute);
|
|
|
+ properties.clear_mouse_mode();
|
|
|
+ properties.clear_cursor_hidden();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (properties.has_cursor_hidden() &&
|
|
|
+ properties.get_cursor_hidden() == (_properties.get_mouse_mode() == WindowProperties::M_relative)) {
|
|
|
+ // A hidden cursor comes for free with pointerlock. Without pointerlock,
|
|
|
+ // though, we can't hide the cursor.
|
|
|
+ properties.clear_cursor_hidden();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Closes the window right now. Called from the window thread.
|
|
|
+ */
|
|
|
+void WebGLGraphicsWindow::
|
|
|
+close_window() {
|
|
|
+ if (_gsg != nullptr) {
|
|
|
+ emscripten_webgl_make_context_current(0);
|
|
|
+ _gsg.clear();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Clear the assigned callbacks.
|
|
|
+ const char *target = "#canvas";
|
|
|
+ emscripten_set_fullscreenchange_callback(target, nullptr, false, nullptr);
|
|
|
+ emscripten_set_pointerlockchange_callback(target, nullptr, false, nullptr);
|
|
|
+ emscripten_set_visibilitychange_callback(nullptr, false, nullptr);
|
|
|
+
|
|
|
+ emscripten_set_focus_callback(target, nullptr, false, nullptr);
|
|
|
+ emscripten_set_blur_callback(target, nullptr, false, nullptr);
|
|
|
+
|
|
|
+ emscripten_set_keypress_callback(target, nullptr, false, nullptr);
|
|
|
+ emscripten_set_keydown_callback(target, nullptr, false, nullptr);
|
|
|
+ emscripten_set_keyup_callback(target, nullptr, false, nullptr);
|
|
|
+
|
|
|
+ //emscripten_set_click_callback(target, nullptr, false, nullptr);
|
|
|
+ emscripten_set_mousedown_callback(target, nullptr, false, nullptr);
|
|
|
+ emscripten_set_mouseup_callback(target, nullptr, false, nullptr);
|
|
|
+ emscripten_set_mousemove_callback(target, nullptr, false, nullptr);
|
|
|
+ emscripten_set_mouseenter_callback(target, nullptr, false, nullptr);
|
|
|
+ emscripten_set_mouseleave_callback(target, nullptr, false, nullptr);
|
|
|
+
|
|
|
+ emscripten_set_wheel_callback(target, nullptr, false, nullptr);
|
|
|
+
|
|
|
+ GraphicsWindow::close_window();
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Opens the window right now. Called from the window thread. Returns true
|
|
|
+ * if the window is successfully opened, or false if there was a problem.
|
|
|
+ */
|
|
|
+bool WebGLGraphicsWindow::
|
|
|
+open_window() {
|
|
|
+ //WebGLGraphicsPipe *webgl_pipe;
|
|
|
+ //DCAST_INTO_R(webgl_pipe, _pipe, false);
|
|
|
+
|
|
|
+ const char *target = "#canvas";
|
|
|
+
|
|
|
+ // GSG Creation/Initialization
|
|
|
+ WebGLGraphicsStateGuardian *webgl_gsg;
|
|
|
+ if (_gsg == nullptr) {
|
|
|
+ // There is no old gsg. Create a new one.
|
|
|
+ webgl_gsg = new WebGLGraphicsStateGuardian(_engine, _pipe);
|
|
|
+ webgl_gsg->choose_pixel_format(_fb_properties, target);
|
|
|
+ _gsg = webgl_gsg;
|
|
|
+ } else {
|
|
|
+ // If the old gsg has the wrong pixel format, create a new one.
|
|
|
+ DCAST_INTO_R(webgl_gsg, _gsg, false);
|
|
|
+ //if (!webgl_gsg->_fb_properties.subsumes(_fb_properties)) {
|
|
|
+ // webgl_gsg = new WebGLGraphicsStateGuardian(_engine, _pipe);
|
|
|
+ // webgl_gsg->choose_pixel_format(_fb_properties, target);
|
|
|
+ // _gsg = webgl_gsg;
|
|
|
+ //}
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!webgl_gsg->_have_context) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (_properties.has_size() && _properties.get_size() != LVecBase2i(1, 1)) {
|
|
|
+ emscripten_set_canvas_element_size(target, _properties.get_x_size(), _properties.get_y_size());
|
|
|
+ } else {
|
|
|
+ int width, height;
|
|
|
+ emscripten_get_canvas_element_size(target, &width, &height);
|
|
|
+ _properties.set_size(width, height);
|
|
|
+
|
|
|
+ EmscriptenFullscreenChangeEvent event;
|
|
|
+ emscripten_get_fullscreen_status(&event);
|
|
|
+ _properties.set_fullscreen(event.isFullscreen);
|
|
|
+ }
|
|
|
+
|
|
|
+ _properties.set_undecorated(true);
|
|
|
+
|
|
|
+ if (emscripten_webgl_make_context_current(webgl_gsg->_context) != EMSCRIPTEN_RESULT_SUCCESS) {
|
|
|
+ webgldisplay_cat.error()
|
|
|
+ << "Failed to make context current.\n";
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ webgl_gsg->reset();
|
|
|
+
|
|
|
+ _fb_properties.clear();
|
|
|
+ _fb_properties.set_rgb_color(true);
|
|
|
+ _fb_properties.set_force_hardware(true);
|
|
|
+ _fb_properties.set_back_buffers(1);
|
|
|
+
|
|
|
+ GLint red_bits, green_bits, blue_bits, alpha_bits, depth_bits, stencil_bits;
|
|
|
+
|
|
|
+ glGetIntegerv(GL_RED_BITS, &red_bits);
|
|
|
+ glGetIntegerv(GL_GREEN_BITS, &green_bits);
|
|
|
+ glGetIntegerv(GL_BLUE_BITS, &blue_bits);
|
|
|
+ glGetIntegerv(GL_ALPHA_BITS, &alpha_bits);
|
|
|
+ glGetIntegerv(GL_DEPTH_BITS, &depth_bits);
|
|
|
+ glGetIntegerv(GL_STENCIL_BITS, &stencil_bits);
|
|
|
+
|
|
|
+ _fb_properties.set_rgba_bits(red_bits, green_bits, blue_bits, alpha_bits);
|
|
|
+ _fb_properties.set_depth_bits(depth_bits);
|
|
|
+ _fb_properties.set_stencil_bits(stencil_bits);
|
|
|
+
|
|
|
+ // Set callbacks.
|
|
|
+ emscripten_set_fullscreenchange_callback(target, (void *)this, false, &on_fullscreen_event);
|
|
|
+ emscripten_set_pointerlockchange_callback(target, (void *)this, false, &on_pointerlock_event);
|
|
|
+ emscripten_set_visibilitychange_callback((void *)this, false, &on_visibility_event);
|
|
|
+
|
|
|
+ emscripten_set_focus_callback(target, (void *)this, false, &on_focus_event);
|
|
|
+ emscripten_set_blur_callback(target, (void *)this, false, &on_focus_event);
|
|
|
+
|
|
|
+ void *user_data = _input;
|
|
|
+ emscripten_set_keypress_callback(target, user_data, false, &on_keyboard_event);
|
|
|
+ emscripten_set_keydown_callback(target, user_data, false, &on_keyboard_event);
|
|
|
+ emscripten_set_keyup_callback(target, user_data, false, &on_keyboard_event);
|
|
|
+
|
|
|
+ //emscripten_set_click_callback(target, user_data, false, &on_mouse_event);
|
|
|
+ emscripten_set_mousedown_callback(target, user_data, false, &on_mouse_event);
|
|
|
+ emscripten_set_mouseup_callback(target, user_data, false, &on_mouse_event);
|
|
|
+ emscripten_set_mousemove_callback(target, user_data, false, &on_mouse_event);
|
|
|
+ emscripten_set_mouseenter_callback(target, user_data, false, &on_mouse_event);
|
|
|
+ emscripten_set_mouseleave_callback(target, user_data, false, &on_mouse_event);
|
|
|
+
|
|
|
+ emscripten_set_wheel_callback(target, user_data, false, &on_wheel_event);
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ *
|
|
|
+ */
|
|
|
+EM_BOOL WebGLGraphicsWindow::
|
|
|
+on_fullscreen_event(int type, const EmscriptenFullscreenChangeEvent *event, void *user_data) {
|
|
|
+ WebGLGraphicsWindow *window = (WebGLGraphicsWindow *)user_data;
|
|
|
+ nassertr(window != nullptr, false);
|
|
|
+
|
|
|
+ if (type == EMSCRIPTEN_EVENT_FULLSCREENCHANGE) {
|
|
|
+ WindowProperties props;
|
|
|
+ props.set_fullscreen(event->isFullscreen);
|
|
|
+ window->system_changed_properties(props);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ *
|
|
|
+ */
|
|
|
+EM_BOOL WebGLGraphicsWindow::
|
|
|
+on_pointerlock_event(int type, const EmscriptenPointerlockChangeEvent *event, void *user_data) {
|
|
|
+ WebGLGraphicsWindow *window = (WebGLGraphicsWindow *)user_data;
|
|
|
+ nassertr(window != nullptr, false);
|
|
|
+
|
|
|
+ if (type == EMSCRIPTEN_EVENT_POINTERLOCKCHANGE) {
|
|
|
+ WindowProperties props;
|
|
|
+ if (event->isActive) {
|
|
|
+ std::cout << "pointerlock engaged\n";
|
|
|
+ props.set_mouse_mode(WindowProperties::M_relative);
|
|
|
+ props.set_cursor_hidden(true);
|
|
|
+ } else {
|
|
|
+ std::cout << "pointerlock disabled\n";
|
|
|
+ props.set_mouse_mode(WindowProperties::M_absolute);
|
|
|
+ props.set_cursor_hidden(false);
|
|
|
+ }
|
|
|
+ window->system_changed_properties(props);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ *
|
|
|
+ */
|
|
|
+EM_BOOL WebGLGraphicsWindow::
|
|
|
+on_visibility_event(int type, const EmscriptenVisibilityChangeEvent *event, void *user_data) {
|
|
|
+ WebGLGraphicsWindow *window = (WebGLGraphicsWindow *)user_data;
|
|
|
+ nassertr(window != nullptr, false);
|
|
|
+
|
|
|
+ if (type == EMSCRIPTEN_EVENT_VISIBILITYCHANGE) {
|
|
|
+ WindowProperties props;
|
|
|
+ props.set_minimized(event->hidden != 0);
|
|
|
+ window->system_changed_properties(props);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ *
|
|
|
+ */
|
|
|
+EM_BOOL WebGLGraphicsWindow::
|
|
|
+on_focus_event(int type, const EmscriptenFocusEvent *event, void *user_data) {
|
|
|
+ WebGLGraphicsWindow *window = (WebGLGraphicsWindow *)user_data;
|
|
|
+ nassertr(window != nullptr, false);
|
|
|
+
|
|
|
+ if (type == EMSCRIPTEN_EVENT_FOCUS) {
|
|
|
+ WindowProperties props;
|
|
|
+ props.set_foreground(true);
|
|
|
+ window->system_changed_properties(props);
|
|
|
+ return true;
|
|
|
+ } else if (type == EMSCRIPTEN_EVENT_BLUR) {
|
|
|
+ WindowProperties props;
|
|
|
+ props.set_foreground(false);
|
|
|
+ window->system_changed_properties(props);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Handles HTML5 keypress, keydown and keyup events.
|
|
|
+ */
|
|
|
+EM_BOOL WebGLGraphicsWindow::
|
|
|
+on_keyboard_event(int type, const EmscriptenKeyboardEvent *event, void *user_data) {
|
|
|
+ GraphicsWindowInputDevice *device;
|
|
|
+ device = (GraphicsWindowInputDevice *)user_data;
|
|
|
+ nassertr(device != nullptr, false);
|
|
|
+
|
|
|
+ if (type == EMSCRIPTEN_EVENT_KEYPRESS) {
|
|
|
+ // We have to use String.fromCharCode to turn this into a text character.
|
|
|
+ // When called from a key event, it does some special magic to ensure that
|
|
|
+ // it does the right thing. We grab the first unicode code point.
|
|
|
+ // Unfortunately, this doesn't seem to handle dead keys on Firefox.
|
|
|
+ int keycode = 0;
|
|
|
+ EM_ASM_({
|
|
|
+ stringToUTF32(String.fromCharCode($0), $1, 4);
|
|
|
+ }, event->charCode, &keycode);
|
|
|
+
|
|
|
+ if (keycode != 0) {
|
|
|
+ device->keystroke(keycode);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ } else if (type == EMSCRIPTEN_EVENT_KEYDOWN ||
|
|
|
+ type == EMSCRIPTEN_EVENT_KEYUP) {
|
|
|
+
|
|
|
+ ButtonHandle handle = map_key(event->which);
|
|
|
+
|
|
|
+ // Send a raw event too, if the browser supports providing it.
|
|
|
+ ButtonHandle raw_handle;
|
|
|
+ if (event->code[0]) {
|
|
|
+ raw_handle = map_raw_key(event->code);
|
|
|
+ } else {
|
|
|
+ // This browser doesn't send raw events. Let's just pretend the user is
|
|
|
+ // using QWERTY. Better than nothing?
|
|
|
+ raw_handle = handle;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (raw_handle != ButtonHandle::none()) {
|
|
|
+ if (type == EMSCRIPTEN_EVENT_KEYUP) {
|
|
|
+ device->raw_button_up(raw_handle);
|
|
|
+ } else if (!event->repeat) {
|
|
|
+ device->raw_button_down(raw_handle);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Send a regular event for the 'virtual' key mapping.
|
|
|
+ if (handle != ButtonHandle::none()) {
|
|
|
+ if (type == EMSCRIPTEN_EVENT_KEYUP) {
|
|
|
+ //webgldisplay_cat.info() << "button up " << handle << "\n";
|
|
|
+ device->button_up(handle);
|
|
|
+ } else if (event->repeat) {
|
|
|
+ device->button_resume_down(handle);
|
|
|
+ } else {
|
|
|
+ //webgldisplay_cat.info() << "button down " << handle << "\n";
|
|
|
+ device->button_down(handle);
|
|
|
+ }
|
|
|
+
|
|
|
+ // If we preventDefault a keydown event, its keypress event won't fire,
|
|
|
+ // which would prevent text input from working. However, we must still
|
|
|
+ // prevent the default action from working in backspace and tab events.
|
|
|
+ if (type == EMSCRIPTEN_EVENT_KEYDOWN &&
|
|
|
+ event->keyCode != 8 && event->keyCode != 9) {
|
|
|
+ return false;
|
|
|
+ } else {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ } else if (event->which != 0) {
|
|
|
+ webgldisplay_cat.info()
|
|
|
+ << "unhandled event code " << event->which << "\n";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Handles mousedown, mouseup, mousemove, mouseenter and mouseleave events.
|
|
|
+ */
|
|
|
+EM_BOOL WebGLGraphicsWindow::
|
|
|
+on_mouse_event(int type, const EmscriptenMouseEvent *event, void *user_data) {
|
|
|
+ GraphicsWindowInputDevice *device;
|
|
|
+ device = (GraphicsWindowInputDevice *)user_data;
|
|
|
+ nassertr(device != nullptr, false);
|
|
|
+
|
|
|
+ const char *target = "#canvas";
|
|
|
+
|
|
|
+ switch (type) {
|
|
|
+ case EMSCRIPTEN_EVENT_MOUSEDOWN:
|
|
|
+ // Don't register out-of-bounds mouse downs.
|
|
|
+ if (event->targetX >= 0 && event->targetY >= 0) {
|
|
|
+ int w, h;
|
|
|
+ emscripten_get_canvas_element_size(target, &w, &h);
|
|
|
+ if (event->targetX < w && event->targetY < h) {
|
|
|
+ device->button_down(MouseButton::button(event->button));
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+
|
|
|
+ case EMSCRIPTEN_EVENT_MOUSEUP:
|
|
|
+ device->button_up(MouseButton::button(event->button));
|
|
|
+ return true;
|
|
|
+
|
|
|
+ case EMSCRIPTEN_EVENT_MOUSEMOVE:
|
|
|
+ {
|
|
|
+ EmscriptenPointerlockChangeEvent ev;
|
|
|
+ emscripten_get_pointerlock_status(&ev);
|
|
|
+
|
|
|
+ if (ev.isActive) {
|
|
|
+ PointerData md = device->get_pointer();
|
|
|
+ device->set_pointer_in_window(md.get_x() + event->movementX,
|
|
|
+ md.get_y() + event->movementY);
|
|
|
+ } else {
|
|
|
+ device->set_pointer_in_window(event->targetX, event->targetY);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+
|
|
|
+ case EMSCRIPTEN_EVENT_MOUSEENTER:
|
|
|
+ break;
|
|
|
+
|
|
|
+ case EMSCRIPTEN_EVENT_MOUSELEAVE:
|
|
|
+ device->set_pointer_out_of_window();
|
|
|
+ return true;
|
|
|
+
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ *
|
|
|
+ */
|
|
|
+EM_BOOL WebGLGraphicsWindow::
|
|
|
+on_wheel_event(int type, const EmscriptenWheelEvent *event, void *user_data) {
|
|
|
+ GraphicsWindowInputDevice *device;
|
|
|
+ device = (GraphicsWindowInputDevice *)user_data;
|
|
|
+ nassertr(device != nullptr, false);
|
|
|
+
|
|
|
+ if (type == EMSCRIPTEN_EVENT_WHEEL) {
|
|
|
+ if (event->deltaY < 0) {
|
|
|
+ device->button_down(MouseButton::wheel_up());
|
|
|
+ device->button_up(MouseButton::wheel_up());
|
|
|
+ }
|
|
|
+ if (event->deltaY > 0) {
|
|
|
+ device->button_down(MouseButton::wheel_down());
|
|
|
+ device->button_up(MouseButton::wheel_down());
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * Maps a JavaScript event keycode to a ButtonHandle.
|
|
|
+ */
|
|
|
+ButtonHandle WebGLGraphicsWindow::
|
|
|
+map_key(int which) {
|
|
|
+ switch (which) {
|
|
|
+ case 8:
|
|
|
+ return KeyboardButton::backspace();
|
|
|
+ case 9:
|
|
|
+ return KeyboardButton::tab();
|
|
|
+ case 13:
|
|
|
+ return KeyboardButton::enter();
|
|
|
+ case 16:
|
|
|
+ return KeyboardButton::shift();
|
|
|
+ case 17:
|
|
|
+ return KeyboardButton::control();
|
|
|
+ case 18:
|
|
|
+ return KeyboardButton::alt();
|
|
|
+ case 19:
|
|
|
+ return KeyboardButton::pause();
|
|
|
+ case 20:
|
|
|
+ return KeyboardButton::caps_lock();
|
|
|
+ case 27:
|
|
|
+ return KeyboardButton::escape();
|
|
|
+ case 32:
|
|
|
+ return KeyboardButton::space();
|
|
|
+ case 33:
|
|
|
+ return KeyboardButton::page_up();
|
|
|
+ case 34:
|
|
|
+ return KeyboardButton::page_down();
|
|
|
+ case 35:
|
|
|
+ return KeyboardButton::end();
|
|
|
+ case 36:
|
|
|
+ return KeyboardButton::home();
|
|
|
+ case 37:
|
|
|
+ return KeyboardButton::left();
|
|
|
+ case 38:
|
|
|
+ return KeyboardButton::up();
|
|
|
+ case 39:
|
|
|
+ return KeyboardButton::right();
|
|
|
+ case 40:
|
|
|
+ return KeyboardButton::down();
|
|
|
+ case 42:
|
|
|
+ return KeyboardButton::print_screen();
|
|
|
+ case 45:
|
|
|
+ return KeyboardButton::insert();
|
|
|
+ case 46:
|
|
|
+ return KeyboardButton::del();
|
|
|
+ case 48:
|
|
|
+ case 49:
|
|
|
+ case 50:
|
|
|
+ case 51:
|
|
|
+ case 52:
|
|
|
+ case 53:
|
|
|
+ case 54:
|
|
|
+ case 55:
|
|
|
+ case 56:
|
|
|
+ case 57:
|
|
|
+ return KeyboardButton::ascii_key('0' + (which - 48));
|
|
|
+ case 59:
|
|
|
+ return KeyboardButton::ascii_key(';');
|
|
|
+ case 61:
|
|
|
+ return KeyboardButton::ascii_key('=');
|
|
|
+ case 65:
|
|
|
+ case 66:
|
|
|
+ case 67:
|
|
|
+ case 68:
|
|
|
+ case 69:
|
|
|
+ case 70:
|
|
|
+ case 71:
|
|
|
+ case 72:
|
|
|
+ case 73:
|
|
|
+ case 74:
|
|
|
+ case 75:
|
|
|
+ case 76:
|
|
|
+ case 77:
|
|
|
+ case 78:
|
|
|
+ case 79:
|
|
|
+ case 80:
|
|
|
+ case 81:
|
|
|
+ case 82:
|
|
|
+ case 83:
|
|
|
+ case 84:
|
|
|
+ case 85:
|
|
|
+ case 86:
|
|
|
+ case 87:
|
|
|
+ case 88:
|
|
|
+ case 89:
|
|
|
+ case 90:
|
|
|
+ return KeyboardButton::ascii_key('a' + (which - 65));
|
|
|
+ case 91:
|
|
|
+ return KeyboardButton::lmeta();
|
|
|
+ case 92:
|
|
|
+ return KeyboardButton::rmeta();
|
|
|
+ case 93:
|
|
|
+ return KeyboardButton::menu();
|
|
|
+ case 96:
|
|
|
+ case 97:
|
|
|
+ case 98:
|
|
|
+ case 99:
|
|
|
+ case 100:
|
|
|
+ case 101:
|
|
|
+ case 102:
|
|
|
+ case 103:
|
|
|
+ case 104:
|
|
|
+ case 105:
|
|
|
+ return KeyboardButton::ascii_key('0' + (which - 96));
|
|
|
+ case 106:
|
|
|
+ return KeyboardButton::ascii_key('*');
|
|
|
+ case 107:
|
|
|
+ return KeyboardButton::ascii_key('+');
|
|
|
+ case 109:
|
|
|
+ return KeyboardButton::ascii_key('-');
|
|
|
+ case 110:
|
|
|
+ return KeyboardButton::ascii_key('.');
|
|
|
+ case 111:
|
|
|
+ return KeyboardButton::ascii_key('/');
|
|
|
+ case 112:
|
|
|
+ return KeyboardButton::f1();
|
|
|
+ case 113:
|
|
|
+ return KeyboardButton::f2();
|
|
|
+ case 114:
|
|
|
+ return KeyboardButton::f3();
|
|
|
+ case 115:
|
|
|
+ return KeyboardButton::f4();
|
|
|
+ case 116:
|
|
|
+ return KeyboardButton::f5();
|
|
|
+ case 117:
|
|
|
+ return KeyboardButton::f6();
|
|
|
+ case 118:
|
|
|
+ return KeyboardButton::f7();
|
|
|
+ case 119:
|
|
|
+ return KeyboardButton::f8();
|
|
|
+ case 120:
|
|
|
+ return KeyboardButton::f9();
|
|
|
+ case 121:
|
|
|
+ return KeyboardButton::f10();
|
|
|
+ case 122:
|
|
|
+ return KeyboardButton::f11();
|
|
|
+ case 123:
|
|
|
+ return KeyboardButton::f12();
|
|
|
+ case 144:
|
|
|
+ return KeyboardButton::num_lock();
|
|
|
+ case 145:
|
|
|
+ return KeyboardButton::scroll_lock();
|
|
|
+ case 173:
|
|
|
+ return KeyboardButton::ascii_key('-');
|
|
|
+ case 186:
|
|
|
+ return KeyboardButton::ascii_key(';');
|
|
|
+ case 187:
|
|
|
+ return KeyboardButton::ascii_key('=');
|
|
|
+ case 188:
|
|
|
+ return KeyboardButton::ascii_key(',');
|
|
|
+ case 189:
|
|
|
+ return KeyboardButton::ascii_key('-');
|
|
|
+ case 190:
|
|
|
+ return KeyboardButton::ascii_key('.');
|
|
|
+ case 191:
|
|
|
+ return KeyboardButton::ascii_key('?');
|
|
|
+ case 192:
|
|
|
+ return KeyboardButton::ascii_key('`');
|
|
|
+ case 219:
|
|
|
+ return KeyboardButton::ascii_key('[');
|
|
|
+ case 220:
|
|
|
+ return KeyboardButton::ascii_key('\\');
|
|
|
+ case 221:
|
|
|
+ return KeyboardButton::ascii_key(']');
|
|
|
+ case 222:
|
|
|
+ return KeyboardButton::ascii_key('\'');
|
|
|
+ default:
|
|
|
+ return ButtonHandle::none();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Maps a HTML5 KeyboardEvent.code string to a ButtonHandle.
|
|
|
+ */
|
|
|
+ButtonHandle WebGLGraphicsWindow::
|
|
|
+map_raw_key(const char *code) {
|
|
|
+ static struct {
|
|
|
+ const char *code;
|
|
|
+ ButtonHandle handle;
|
|
|
+ } mappings[] = {
|
|
|
+ //{"Again", KeyboardButton::()},
|
|
|
+ {"AltLeft", KeyboardButton::lalt()},
|
|
|
+ {"AltRight", KeyboardButton::ralt()},
|
|
|
+ {"ArrowDown", KeyboardButton::down()},
|
|
|
+ {"ArrowLeft", KeyboardButton::left()},
|
|
|
+ {"ArrowRight", KeyboardButton::right()},
|
|
|
+ {"ArrowUp", KeyboardButton::up()},
|
|
|
+ {"Backquote", KeyboardButton::ascii_key('`')},
|
|
|
+ {"Backslash", KeyboardButton::ascii_key('\\')},
|
|
|
+ {"Backspace", KeyboardButton::backspace()},
|
|
|
+ {"BracketLeft", KeyboardButton::ascii_key('[')},
|
|
|
+ {"BracketRight", KeyboardButton::ascii_key(']')},
|
|
|
+ //{"BrowserBack", KeyboardButton::()},
|
|
|
+ //{"BrowserFavorites", KeyboardButton::()},
|
|
|
+ //{"BrowserForward", KeyboardButton::()},
|
|
|
+ //{"BrowserRefresh", KeyboardButton::()},
|
|
|
+ //{"BrowserSearch", KeyboardButton::()},
|
|
|
+ //{"BrowserStop", KeyboardButton::()},
|
|
|
+ {"CapsLock", KeyboardButton::caps_lock()},
|
|
|
+ {"Comma", KeyboardButton::ascii_key(',')},
|
|
|
+ {"ContextMenu", KeyboardButton::menu()},
|
|
|
+ {"ControlLeft", KeyboardButton::lcontrol()},
|
|
|
+ {"ControlRight", KeyboardButton::rcontrol()},
|
|
|
+ //{"Convert", KeyboardButton::()},
|
|
|
+ //{"Copy", KeyboardButton::()},
|
|
|
+ //{"Cut", KeyboardButton::()},
|
|
|
+ {"Delete", KeyboardButton::del()},
|
|
|
+ {"Digit0", KeyboardButton::ascii_key('0')},
|
|
|
+ {"Digit1", KeyboardButton::ascii_key('1')},
|
|
|
+ {"Digit2", KeyboardButton::ascii_key('2')},
|
|
|
+ {"Digit3", KeyboardButton::ascii_key('3')},
|
|
|
+ {"Digit4", KeyboardButton::ascii_key('4')},
|
|
|
+ {"Digit5", KeyboardButton::ascii_key('5')},
|
|
|
+ {"Digit6", KeyboardButton::ascii_key('6')},
|
|
|
+ {"Digit7", KeyboardButton::ascii_key('7')},
|
|
|
+ {"Digit8", KeyboardButton::ascii_key('8')},
|
|
|
+ {"Digit9", KeyboardButton::ascii_key('9')},
|
|
|
+ //{"Eject", KeyboardButton::()},
|
|
|
+ {"End", KeyboardButton::end()},
|
|
|
+ {"Enter", KeyboardButton::enter()},
|
|
|
+ {"Equal", KeyboardButton::ascii_key('=')},
|
|
|
+ {"Escape", KeyboardButton::escape()},
|
|
|
+ {"F1", KeyboardButton::f1()},
|
|
|
+ {"F10", KeyboardButton::f10()},
|
|
|
+ {"F11", KeyboardButton::f11()},
|
|
|
+ {"F12", KeyboardButton::f12()},
|
|
|
+ {"F13", KeyboardButton::f13()},
|
|
|
+ {"F14", KeyboardButton::f14()},
|
|
|
+ {"F15", KeyboardButton::f15()},
|
|
|
+ {"F16", KeyboardButton::f16()},
|
|
|
+ //{"F17", KeyboardButton::f17()},
|
|
|
+ //{"F18", KeyboardButton::f18()},
|
|
|
+ //{"F19", KeyboardButton::f19()},
|
|
|
+ {"F2", KeyboardButton::f2()},
|
|
|
+ //{"F20", KeyboardButton::f20()},
|
|
|
+ //{"F21", KeyboardButton::f21()},
|
|
|
+ //{"F22", KeyboardButton::f22()},
|
|
|
+ //{"F23", KeyboardButton::f23()},
|
|
|
+ //{"F24", KeyboardButton::f24()},
|
|
|
+ {"F3", KeyboardButton::f3()},
|
|
|
+ {"F4", KeyboardButton::f4()},
|
|
|
+ {"F5", KeyboardButton::f5()},
|
|
|
+ {"F6", KeyboardButton::f6()},
|
|
|
+ {"F7", KeyboardButton::f7()},
|
|
|
+ {"F8", KeyboardButton::f8()},
|
|
|
+ {"F9", KeyboardButton::f9()},
|
|
|
+ //{"Find", KeyboardButton::()},
|
|
|
+ //{"Fn", KeyboardButton::()},
|
|
|
+ {"Help", KeyboardButton::help()},
|
|
|
+ {"Home", KeyboardButton::home()},
|
|
|
+ {"Insert", KeyboardButton::insert()},
|
|
|
+ //{"IntlBackslash", KeyboardButton::()},
|
|
|
+ //{"IntlRo", KeyboardButton::()},
|
|
|
+ //{"IntlYen", KeyboardButton::()},
|
|
|
+ //{"KanaMode", KeyboardButton::()},
|
|
|
+ {"KeyA", KeyboardButton::ascii_key('a')},
|
|
|
+ {"KeyB", KeyboardButton::ascii_key('b')},
|
|
|
+ {"KeyC", KeyboardButton::ascii_key('c')},
|
|
|
+ {"KeyD", KeyboardButton::ascii_key('d')},
|
|
|
+ {"KeyE", KeyboardButton::ascii_key('e')},
|
|
|
+ {"KeyF", KeyboardButton::ascii_key('f')},
|
|
|
+ {"KeyG", KeyboardButton::ascii_key('g')},
|
|
|
+ {"KeyH", KeyboardButton::ascii_key('h')},
|
|
|
+ {"KeyI", KeyboardButton::ascii_key('i')},
|
|
|
+ {"KeyJ", KeyboardButton::ascii_key('j')},
|
|
|
+ {"KeyK", KeyboardButton::ascii_key('k')},
|
|
|
+ {"KeyL", KeyboardButton::ascii_key('l')},
|
|
|
+ {"KeyM", KeyboardButton::ascii_key('m')},
|
|
|
+ {"KeyN", KeyboardButton::ascii_key('n')},
|
|
|
+ {"KeyO", KeyboardButton::ascii_key('o')},
|
|
|
+ {"KeyP", KeyboardButton::ascii_key('p')},
|
|
|
+ {"KeyQ", KeyboardButton::ascii_key('q')},
|
|
|
+ {"KeyR", KeyboardButton::ascii_key('r')},
|
|
|
+ {"KeyS", KeyboardButton::ascii_key('s')},
|
|
|
+ {"KeyT", KeyboardButton::ascii_key('t')},
|
|
|
+ {"KeyU", KeyboardButton::ascii_key('u')},
|
|
|
+ {"KeyV", KeyboardButton::ascii_key('v')},
|
|
|
+ {"KeyW", KeyboardButton::ascii_key('w')},
|
|
|
+ {"KeyX", KeyboardButton::ascii_key('x')},
|
|
|
+ {"KeyY", KeyboardButton::ascii_key('y')},
|
|
|
+ {"KeyZ", KeyboardButton::ascii_key('z')},
|
|
|
+ //{"Lang1", KeyboardButton::()},
|
|
|
+ //{"Lang2", KeyboardButton::()},
|
|
|
+ //{"LaunchApp1", KeyboardButton::()},
|
|
|
+ //{"MediaPlayPause", KeyboardButton::()},
|
|
|
+ //{"MediaStop", KeyboardButton::()},
|
|
|
+ //{"MediaTrackNext", KeyboardButton::()},
|
|
|
+ //{"MediaTrackPrevious", KeyboardButton::()},
|
|
|
+ {"Minus", KeyboardButton::ascii_key('-')},
|
|
|
+ //{"NonConvert", KeyboardButton::()},
|
|
|
+ {"NumLock", KeyboardButton::num_lock()},
|
|
|
+ {"Numpad0", KeyboardButton::ascii_key('0')},
|
|
|
+ {"Numpad1", KeyboardButton::ascii_key('1')},
|
|
|
+ {"Numpad2", KeyboardButton::ascii_key('2')},
|
|
|
+ {"Numpad3", KeyboardButton::ascii_key('3')},
|
|
|
+ {"Numpad4", KeyboardButton::ascii_key('4')},
|
|
|
+ {"Numpad5", KeyboardButton::ascii_key('5')},
|
|
|
+ {"Numpad6", KeyboardButton::ascii_key('6')},
|
|
|
+ {"Numpad7", KeyboardButton::ascii_key('7')},
|
|
|
+ {"Numpad8", KeyboardButton::ascii_key('8')},
|
|
|
+ {"Numpad9", KeyboardButton::ascii_key('9')},
|
|
|
+ {"NumpadAdd", KeyboardButton::ascii_key('+')},
|
|
|
+ {"NumpadComma", KeyboardButton::ascii_key(',')},
|
|
|
+ {"NumpadDecimal", KeyboardButton::ascii_key('.')},
|
|
|
+ {"NumpadDivide", KeyboardButton::ascii_key('/')},
|
|
|
+ {"NumpadEnter", KeyboardButton::enter()},
|
|
|
+ {"NumpadEqual", KeyboardButton::ascii_key('=')},
|
|
|
+ {"NumpadMultiply", KeyboardButton::ascii_key('*')},
|
|
|
+ {"NumpadSubtract", KeyboardButton::ascii_key('-')},
|
|
|
+ //{"Open", KeyboardButton::()},
|
|
|
+ {"OSLeft", KeyboardButton::lmeta()},
|
|
|
+ {"OSRight", KeyboardButton::rmeta()},
|
|
|
+ {"PageDown", KeyboardButton::page_down()},
|
|
|
+ {"PageUp", KeyboardButton::page_up()},
|
|
|
+ //{"Paste", KeyboardButton::()},
|
|
|
+ {"Pause", KeyboardButton::pause()},
|
|
|
+ {"Period", KeyboardButton::ascii_key('.')},
|
|
|
+ //{"Power", KeyboardButton::()},
|
|
|
+ {"PrintScreen", KeyboardButton::print_screen()},
|
|
|
+ //{"Props", KeyboardButton::()},
|
|
|
+ {"Quote", KeyboardButton::ascii_key('\'')},
|
|
|
+ {"ScrollLock", KeyboardButton::scroll_lock()},
|
|
|
+ //{"Select", KeyboardButton::()},
|
|
|
+ {"Semicolon", KeyboardButton::ascii_key(';')},
|
|
|
+ {"ShiftLeft", KeyboardButton::lshift()},
|
|
|
+ {"ShiftRight", KeyboardButton::rshift()},
|
|
|
+ {"Slash", KeyboardButton::ascii_key('/')},
|
|
|
+ //{"Sleep", KeyboardButton::()},
|
|
|
+ {"Space", KeyboardButton::ascii_key(' ')},
|
|
|
+ {"Tab", KeyboardButton::tab()},
|
|
|
+ //{"Undo", KeyboardButton::()},
|
|
|
+ //{"VolumeDown", KeyboardButton::()},
|
|
|
+ //{"VolumeMute", KeyboardButton::()},
|
|
|
+ //{"VolumeUp", KeyboardButton::()},
|
|
|
+ //{"WakeUp", KeyboardButton::()},
|
|
|
+ {nullptr, ButtonHandle::none()}
|
|
|
+ };
|
|
|
+
|
|
|
+ for (int i = 0; mappings[i].code; ++i) {
|
|
|
+ int cmp = strcmp(mappings[i].code, code);
|
|
|
+ if (cmp == 0) {
|
|
|
+ return mappings[i].handle;
|
|
|
+ } else if (cmp > 0) {
|
|
|
+ // They're in alphabetical order, and we've passed it by, so bail early.
|
|
|
+ return ButtonHandle::none();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return ButtonHandle::none();
|
|
|
+}
|