diff --git a/menu/drivers/materialui.c b/menu/drivers/materialui.c index 2c0da7e616..f35b25568e 100644 --- a/menu/drivers/materialui.c +++ b/menu/drivers/materialui.c @@ -143,6 +143,7 @@ enum typedef struct materialui_handle { bool need_compute; + bool need_scroll; bool mouse_show; int cursor_size; @@ -645,6 +646,40 @@ static void materialui_compute_entries_box(materialui_handle_t* mui, int width, mui->content_height = sum; } +/* Compute the scroll value depending on the highlighted entry */ +static float materialui_get_scroll(materialui_handle_t *mui) +{ + unsigned i, width, height = 0; + float half, sum = 0; + size_t selection = menu_navigation_get_selection(); + file_list_t *list = menu_entries_get_selection_buf_ptr(0); + + if (!mui) + return 0; + + /* Whenever we perform a 'manual' scroll, scroll + * acceleration must be reset */ + menu_input_set_pointer_y_accel(0.0f); + + video_driver_get_size(&width, &height); + + half = height / 2; + + for (i = 0; i < selection; i++) + { + materialui_node_t *node = (materialui_node_t*) + file_list_get_userdata_at_offset(list, i); + + if (node) + sum += node->line_height; + } + + if (sum < half) + return 0; + + return sum - half; +} + /* Called on each frame. We use this callback to implement the touch scroll with acceleration */ static void materialui_render(void *data, @@ -652,6 +687,7 @@ static void materialui_render(void *data, bool is_idle) { unsigned bottom, header_height; + menu_input_pointer_t pointer; size_t i = 0; materialui_handle_t *mui = (materialui_handle_t*)data; settings_t *settings = config_get_ptr(); @@ -660,61 +696,63 @@ static void materialui_render(void *data, if (!mui) return; + /* Here's a nasty issue: + * After calling populate_entries(), we need to call + * materialui_get_scroll() so the last selected item + * is correctly displayed on screen. + * But we can't do this until materialui_compute_entries_box() + * has been called, so we should delegate it until mui->need_compute + * is acted upon. + * *But* we can't do this in the same frame that mui->need_compute + * is acted upon, because of the order in which materialui_frame() + * and materialui_render() are called. Since mui->tabs_height is + * set by materialui_frame(), the first time materialui_render() is + * called after populate_entries() it has the wrong mui->tabs_height + * value... + * We therefore have to delegate the scroll until the frame after + * mui->need_compute is handled... */ + if (mui->need_scroll) + { + mui->scroll_y = materialui_get_scroll(mui); + mui->need_scroll = false; + } + if (mui->need_compute) { if (mui->font) materialui_compute_entries_box(mui, width, height); mui->need_compute = false; + mui->need_scroll = true; } menu_display_set_width(width); menu_display_set_height(height); header_height = menu_display_get_header_height(); - if (settings->bools.menu_pointer_enable) + menu_input_get_pointer_state(&pointer); + + if (pointer.type != MENU_POINTER_DISABLED) { size_t ii; - int16_t pointer_y = menu_input_pointer_state(MENU_POINTER_Y_AXIS); - float old_accel_val = 0.0f; - float new_accel_val = 0.0f; - size_t entries_end = menu_entries_get_size(); + int16_t pointer_y = pointer.y; + float old_accel_val = 0.0f; + float new_accel_val = 0.0f; + size_t entries_end = menu_entries_get_size(); for (ii = 0; ii < entries_end; ii++) { materialui_node_t *node = (materialui_node_t*) file_list_get_userdata_at_offset(list, ii); - if (pointer_y > (-mui->scroll_y + header_height + node->y) - && pointer_y < (-mui->scroll_y + header_height + node->y + node->line_height) - ) - menu_input_ctl(MENU_INPUT_CTL_POINTER_PTR, &ii); + if ((pointer_y > (-mui->scroll_y + header_height + node->y)) && + (pointer_y < (-mui->scroll_y + header_height + node->y + node->line_height))) + { + menu_input_set_pointer_selection(ii); + break; + } } - menu_input_ctl(MENU_INPUT_CTL_POINTER_ACCEL_READ, &old_accel_val); - - mui->scroll_y -= old_accel_val; - - new_accel_val = old_accel_val * 0.96; - - menu_input_ctl(MENU_INPUT_CTL_POINTER_ACCEL_WRITE, &new_accel_val); - } - - if (settings->bools.menu_mouse_enable) - { - size_t ii; - int16_t mouse_y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); - size_t entries_end = menu_entries_get_size(); - - for (ii = 0; ii < entries_end; ii++) - { - materialui_node_t *node = (materialui_node_t*) - file_list_get_userdata_at_offset(list, ii); - - if (mouse_y > (-mui->scroll_y + header_height + node->y) - && mouse_y < (-mui->scroll_y + header_height + node->y + node->line_height) - ) - menu_input_ctl(MENU_INPUT_CTL_MOUSE_PTR, &ii); - } + mui->scroll_y -= pointer.y_accel; } if (mui->scroll_y < 0) @@ -1664,15 +1702,20 @@ static void materialui_frame(void *data, video_frame_info_t *video_info) } if (mui->mouse_show) + { + menu_input_pointer_t pointer; + menu_input_get_pointer_state(&pointer); + menu_display_draw_cursor( video_info, &white_bg[0], mui->cursor_size, mui->textures.list[MUI_TEXTURE_POINTER], - menu_input_mouse_state(MENU_MOUSE_X_AXIS), - menu_input_mouse_state(MENU_MOUSE_Y_AXIS), + pointer.x, + pointer.y, width, height); + } menu_display_restore_clear_color(); menu_display_unset_viewport(video_info->width, video_info->height); @@ -1766,6 +1809,7 @@ static void *materialui_init(void **userdata, bool video_is_threaded) *userdata = mui; mui->cursor_size = scale_factor / 3; mui->need_compute = false; + mui->need_scroll = false; mui->menu_title[0] = '\0'; @@ -1840,36 +1884,6 @@ static bool materialui_load_image(void *userdata, void *data, enum menu_image_ty return true; } -/* Compute the scroll value depending on the highlighted entry */ -static float materialui_get_scroll(materialui_handle_t *mui) -{ - unsigned i, width, height = 0; - float half, sum = 0; - size_t selection = menu_navigation_get_selection(); - file_list_t *list = menu_entries_get_selection_buf_ptr(0); - - if (!mui) - return 0; - - video_driver_get_size(&width, &height); - - half = height / 2; - - for (i = 0; i < selection; i++) - { - materialui_node_t *node = (materialui_node_t*) - file_list_get_userdata_at_offset(list, i); - - if (node) - sum += node->line_height; - } - - if (sum < half) - return 0; - - return sum - half; -} - /* The navigation pointer has been updated (for example by pressing up or down on the keyboard). We use this function to animate the scroll. */ static void materialui_navigation_set(void *data, bool scroll) @@ -1881,6 +1895,11 @@ static void materialui_navigation_set(void *data, bool scroll) if (!mui || !scroll) return; + /* mui->scroll_y will be modified by the animation + * - Set scroll acceleration to zero to minimise + * potential conflicts */ + menu_input_set_pointer_y_accel(0.0f); + entry.duration = 166; entry.target_value = scroll_pos; entry.subject = &mui->scroll_y; @@ -1895,7 +1914,11 @@ static void materialui_navigation_set(void *data, bool scroll) static void materialui_list_set_selection(void *data, file_list_t *list) { - materialui_navigation_set(data, true); + /* This is called upon MENU_ACTION_CANCEL + * Have to set 'scroll' to false, otherwise + * navigating backwards in the menu is absolutely + * horrendous... */ + materialui_navigation_set(data, false); } /* The navigation pointer is set back to zero */ @@ -1908,6 +1931,7 @@ static void materialui_navigation_clear(void *data, bool pending_push) menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &i); mui->scroll_y = 0; + menu_input_set_pointer_y_accel(0.0f); } static void materialui_navigation_set_last(void *data) @@ -1925,13 +1949,18 @@ static void materialui_populate_entries( void *data, const char *path, const char *label, unsigned i) { - materialui_handle_t *mui = (materialui_handle_t*)data; + materialui_handle_t *mui = (materialui_handle_t*)data; + if (!mui) return; menu_entries_get_title(mui->menu_title, sizeof(mui->menu_title)); mui->need_compute = true; - mui->scroll_y = materialui_get_scroll(mui); + + /* Note: mui->scroll_y position needs to be set here, + * but we can't do this until materialui_compute_entries_box() + * has been called. We therefore delegate it until mui->need_compute + * is acted upon */ } /* Context reset is called on launch or when a core is launched */ @@ -2902,7 +2931,6 @@ menu_ctx_driver_t menu_ctx_mui = { NULL, NULL, NULL, - NULL, menu_display_osk_ptr_at_pos, NULL, /* update_savestate_thumbnail_path */ NULL, /* update_savestate_thumbnail_image */ diff --git a/menu/drivers/null.c b/menu/drivers/null.c index d68d1d7921..0e958201bc 100644 --- a/menu/drivers/null.c +++ b/menu/drivers/null.c @@ -88,7 +88,6 @@ menu_ctx_driver_t menu_ctx_null = { NULL, /* load_image */ "null", NULL, /* environ */ - NULL, /* pointer_tap */ NULL, /* update_thumbnail_path */ NULL, /* update_thumbnail_image */ NULL, /* refresh_thumbnail_image */ diff --git a/menu/drivers/ozone/ozone.c b/menu/drivers/ozone/ozone.c index 3e7ab817f4..c0b05dcace 100644 --- a/menu/drivers/ozone/ozone.c +++ b/menu/drivers/ozone/ozone.c @@ -1529,8 +1529,11 @@ static void ozone_frame(void *data, video_frame_info_t *video_info) if (ozone->first_frame) { - ozone->cursor_x_old = menu_input_mouse_state(MENU_MOUSE_X_AXIS); - ozone->cursor_y_old = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); + menu_input_pointer_t pointer; + menu_input_get_pointer_state(&pointer); + + ozone->cursor_x_old = pointer.x; + ozone->cursor_y_old = pointer.y; ozone->first_frame = false; } @@ -1699,14 +1702,17 @@ static void ozone_frame(void *data, video_frame_info_t *video_info) /* Cursor */ if (ozone->show_cursor) { + menu_input_pointer_t pointer; + menu_input_get_pointer_state(&pointer); + menu_display_set_alpha(ozone_pure_white, 1.0f); menu_display_draw_cursor( video_info, ozone_pure_white, ozone->dimensions.cursor_size, ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_POINTER], - menu_input_mouse_state(MENU_MOUSE_X_AXIS), - menu_input_mouse_state(MENU_MOUSE_Y_AXIS), + pointer.x, + pointer.y, video_info->width, video_info->height ); @@ -2269,13 +2275,13 @@ static bool ozone_get_load_content_animation_data(void *userdata, menu_texture_i } #endif -static int ozone_pointer_tap(void *userdata, +static int ozone_pointer_up(void *userdata, unsigned x, unsigned y, unsigned ptr, menu_file_list_cbs_t *cbs, menu_entry_t *entry, unsigned action) { size_t selection = menu_navigation_get_selection(); - if (ptr == selection && cbs && cbs->action_select) + if (ptr == selection) return (unsigned)menu_entry_action(entry, (unsigned)selection, MENU_ACTION_SELECT); menu_navigation_set_selection(ptr); @@ -2435,7 +2441,6 @@ menu_ctx_driver_t menu_ctx_ozone = { ozone_load_image, "ozone", ozone_environ_cb, - ozone_pointer_tap, ozone_update_thumbnail_path, ozone_update_thumbnail_image, ozone_refresh_thumbnail_image, @@ -2446,7 +2451,7 @@ menu_ctx_driver_t menu_ctx_ozone = { NULL, /* update_savestate_thumbnail_path */ NULL, /* update_savestate_thumbnail_image */ NULL, /* pointer_down */ - NULL, /* pointer_up */ + ozone_pointer_up, #ifdef HAVE_MENU_WIDGETS ozone_get_load_content_animation_data #else diff --git a/menu/drivers/ozone/ozone_entries.c b/menu/drivers/ozone/ozone_entries.c index 366febf569..62377ddbac 100644 --- a/menu/drivers/ozone/ozone_entries.c +++ b/menu/drivers/ozone/ozone_entries.c @@ -351,6 +351,7 @@ void ozone_draw_entries(ozone_handle_t *ozone, video_frame_info_t *video_info, size_t i, y, entries_end; float sidebar_offset, bottom_boundary, invert, alpha_anim; unsigned video_info_height, video_info_width, entry_width, button_height; + menu_input_pointer_t pointer; settings_t *settings = config_get_ptr(); bool old_list = selection_buf == ozone->selection_buf_old; @@ -359,13 +360,24 @@ void ozone_draw_entries(ozone_handle_t *ozone, video_frame_info_t *video_info, size_t old_selection_y = 0; int entry_padding = ozone_get_entries_padding(ozone, old_list); - int16_t cursor_x = menu_input_mouse_state(MENU_MOUSE_X_AXIS); - int16_t cursor_y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); + int16_t cursor_x = 0; + int16_t cursor_y = 0; - if (settings->bools.menu_mouse_enable && !ozone->cursor_mode && (cursor_x != ozone->cursor_x_old || cursor_y != ozone->cursor_y_old)) - ozone->cursor_mode = true; - else if (!settings->bools.menu_mouse_enable) - ozone->cursor_mode = false; /* we need to disable it on the fly */ + menu_input_get_pointer_state(&pointer); + + if (pointer.type != MENU_POINTER_DISABLED) + { + cursor_x = pointer.x; + cursor_y = pointer.y; + + /* Not sure why it's done like this - best to leave well alone for now... */ + if (settings->bools.menu_mouse_enable && !ozone->cursor_mode && (cursor_x != ozone->cursor_x_old || cursor_y != ozone->cursor_y_old)) + ozone->cursor_mode = true; + else if (!settings->bools.menu_mouse_enable) + ozone->cursor_mode = false; /* we need to disable it on the fly */ + } + else + ozone->cursor_mode = false; ozone->cursor_x_old = cursor_x; ozone->cursor_y_old = cursor_y; @@ -438,15 +450,9 @@ void ozone_draw_entries(ozone_handle_t *ozone, video_frame_info_t *video_info, /* Cursor */ if (!old_list && ozone->cursor_mode) - { if ( cursor_x >= border_start_x && (cursor_x <= border_start_x + (int)entry_width) && cursor_y >= border_start_y && (cursor_y <= border_start_y + (int)button_height)) - { - selection_y = y; - menu_navigation_set_selection(i); - menu_input_ctl(MENU_INPUT_CTL_MOUSE_PTR, &i); - } - } + menu_input_set_pointer_selection(i); border_iterate: if (node) diff --git a/menu/drivers/rgui.c b/menu/drivers/rgui.c index 84a4e6d0a8..174b82fcac 100644 --- a/menu/drivers/rgui.c +++ b/menu/drivers/rgui.c @@ -531,7 +531,7 @@ typedef struct bool shadow_enable; unsigned particle_effect; bool extended_ascii_enable; - float scroll_y; + int16_t scroll_y; char msgbox[1024]; unsigned color_theme; rgui_colors_t colors; @@ -2824,23 +2824,80 @@ end: string_list_free(list); } -static void rgui_blit_cursor(void) +static void rgui_blit_cursor(rgui_t *rgui) { size_t fb_pitch; unsigned fb_width, fb_height; - int16_t x = menu_input_mouse_state(MENU_MOUSE_X_AXIS); - int16_t y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); + menu_input_pointer_t pointer; menu_display_get_fb_size(&fb_width, &fb_height, &fb_pitch); + menu_input_get_pointer_state(&pointer); + if (rgui_frame_buf.data) { - rgui_color_rect(rgui_frame_buf.data, fb_width, fb_height, x, y - 5, 1, 11, 0xFFFF); - rgui_color_rect(rgui_frame_buf.data, fb_width, fb_height, x - 5, y, 11, 1, 0xFFFF); + rgui_color_rect(rgui_frame_buf.data, fb_width, fb_height, pointer.x, pointer.y - 5, 1, 11, rgui->colors.normal_color); + rgui_color_rect(rgui_frame_buf.data, fb_width, fb_height, pointer.x - 5, pointer.y, 11, 1, rgui->colors.normal_color); } } +int rgui_osk_ptr_at_pos(void *data, int x, int y, + unsigned width, unsigned height) +{ + /* This is a lazy copy/paste from rgui_render_osk(), + * but it will do for now... */ + size_t fb_pitch; + unsigned fb_width, fb_height; + size_t key_index; + + unsigned key_width, key_height; + unsigned key_text_offset_x, key_text_offset_y; + unsigned ptr_width, ptr_height; + unsigned ptr_offset_x, ptr_offset_y; + + unsigned keyboard_width, keyboard_height; + unsigned keyboard_offset_x, keyboard_offset_y; + + unsigned osk_width, osk_height; + unsigned osk_x, osk_y; + + /* Get dimensions/layout */ + menu_display_get_fb_size(&fb_width, &fb_height, &fb_pitch); + + key_text_offset_x = 8; + key_text_offset_y = 6; + key_width = FONT_WIDTH + (key_text_offset_x * 2); + key_height = FONT_HEIGHT + (key_text_offset_y * 2); + ptr_offset_x = 2; + ptr_offset_y = 2; + ptr_width = key_width - (ptr_offset_x * 2); + ptr_height = key_height - (ptr_offset_y * 2); + keyboard_width = key_width * OSK_CHARS_PER_LINE; + keyboard_height = key_height * 4; + keyboard_offset_x = 10; + keyboard_offset_y = 10 + 15 + (2 * FONT_HEIGHT_STRIDE); + osk_width = keyboard_width + 20; + osk_height = keyboard_offset_y + keyboard_height + 10; + osk_x = (fb_width - osk_width) / 2; + osk_y = (fb_height - osk_height) / 2; + + for (key_index = 0; key_index < 44; key_index++) + { + unsigned key_row = (unsigned)(key_index / OSK_CHARS_PER_LINE); + unsigned key_column = (unsigned)(key_index - (key_row * OSK_CHARS_PER_LINE)); + + unsigned osk_ptr_x = osk_x + keyboard_offset_x + ptr_offset_x + (key_column * key_width); + unsigned osk_ptr_y = osk_y + keyboard_offset_y + ptr_offset_y + (key_row * key_height); + + if (x > osk_ptr_x && x < osk_ptr_x + ptr_width && + y > osk_ptr_y && y < osk_ptr_y + ptr_height) + return (int)key_index; + } + + return -1; +} + static void rgui_render_osk( rgui_t *rgui, menu_animation_ctx_ticker_t *ticker, menu_animation_ctx_ticker_smooth_t *ticker_smooth, @@ -3134,8 +3191,9 @@ static void rgui_render(void *data, size_t i, end, fb_pitch, old_start, new_start; unsigned fb_width, fb_height; int bottom; + menu_input_pointer_t pointer; unsigned ticker_x_offset = 0; - size_t entries_end = 0; + size_t entries_end = menu_entries_get_size(); bool msg_force = false; bool fb_size_changed = false; settings_t *settings = config_get_ptr(); @@ -3144,6 +3202,11 @@ static void rgui_render(void *data, static bool display_kb = false; bool current_display_cb = false; + bool show_fs_thumbnail = + rgui->show_fs_thumbnail && + rgui->entry_has_thumbnail && + (fs_thumbnail.is_valid || (rgui->thumbnail_queue_size > 0)); + /* Sanity check */ if (!rgui || !rgui_frame_buf.data || !settings) return; @@ -3207,64 +3270,73 @@ static void rgui_render(void *data, rgui->force_redraw = false; - if (settings->bools.menu_pointer_enable) + /* Get offset of bottommost entry */ + bottom = (int)(entries_end - rgui_term_layout.height); + menu_entries_ctl(MENU_ENTRIES_CTL_START_GET, &old_start); + if (old_start > (unsigned)bottom) { - unsigned new_val; + /* MENU_ENTRIES_CTL_SET_START requires a pointer of + * type size_t, so have to create a copy of 'bottom' + * here to avoid memory errors... */ + size_t bottom_cpy = (size_t)bottom; + menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &bottom_cpy); + } - menu_entries_ctl(MENU_ENTRIES_CTL_START_GET, &old_start); + /* Handle pointer input + * Note: We'd normally just check pointer.type here but + * RGUI focuses on performance - so skip all of this + * if both mouse and touchscreen input are disabled by + * the user (only saves a dozen or so clock cycles, but + * might as well...) */ + if (settings->bools.menu_mouse_enable || settings->bools.menu_pointer_enable) + { + menu_input_get_pointer_state(&pointer); - new_val = (unsigned)(menu_input_pointer_state(MENU_POINTER_Y_AXIS) - / (11 - 2 + old_start)); - - menu_input_ctl(MENU_INPUT_CTL_POINTER_PTR, &new_val); - - if (menu_input_ctl(MENU_INPUT_CTL_IS_POINTER_DRAGGED, NULL)) + /* Ignore input when showing a fullscreen thumbnail */ + if ((pointer.type != MENU_POINTER_DISABLED) && !show_fs_thumbnail) { - size_t start; - int16_t delta_y = menu_input_pointer_state(MENU_POINTER_DELTA_Y_AXIS); - rgui->scroll_y += delta_y; + /* Update currently 'highlighted' item */ + if (pointer.y > rgui_term_layout.start_y) + { + unsigned new_ptr; + menu_entries_ctl(MENU_ENTRIES_CTL_START_GET, &old_start); - start = -rgui->scroll_y / 11 + 2; + /* Note: It's okay for this to go out of range + * (limits are checked in rgui_pointer_up()) */ + new_ptr = (unsigned)((pointer.y - rgui_term_layout.start_y) / FONT_HEIGHT_STRIDE) + old_start; - menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &start); + menu_input_set_pointer_selection(new_ptr); + } - if (rgui->scroll_y > 0) - rgui->scroll_y = 0; + /* Allow drag-scrolling if items are currently off-screen */ + if (pointer.dragged && (bottom > 0)) + { + size_t start; + int16_t scroll_y_max = bottom * FONT_HEIGHT_STRIDE; + + rgui->scroll_y += -1 * pointer.dy; + rgui->scroll_y = (rgui->scroll_y < 0) ? 0 : rgui->scroll_y; + rgui->scroll_y = (rgui->scroll_y > scroll_y_max) ? scroll_y_max : rgui->scroll_y; + + start = rgui->scroll_y / FONT_HEIGHT_STRIDE; + menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &start); + } } } - if (settings->bools.menu_mouse_enable) - { - unsigned new_mouse_ptr; - int16_t mouse_y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); - - menu_entries_ctl(MENU_ENTRIES_CTL_START_GET, &old_start); - - new_mouse_ptr = (unsigned)(mouse_y / 11 - 2 + old_start); - - menu_input_ctl(MENU_INPUT_CTL_MOUSE_PTR, &new_mouse_ptr); - } + /* Start position may have changed - get current + * value and determine index of last displayed entry */ + menu_entries_ctl(MENU_ENTRIES_CTL_START_GET, &old_start); + end = ((old_start + rgui_term_layout.height) <= entries_end) ? + old_start + rgui_term_layout.height : entries_end; /* Do not scroll if all items are visible. */ - if (menu_entries_get_size() <= rgui_term_layout.height) + if (entries_end <= rgui_term_layout.height) { size_t start = 0; menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &start); } - bottom = (int)(menu_entries_get_size() - rgui_term_layout.height); - menu_entries_ctl(MENU_ENTRIES_CTL_START_GET, &old_start); - - if (old_start > (unsigned)bottom) - menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &bottom); - - menu_entries_ctl(MENU_ENTRIES_CTL_START_GET, &old_start); - - entries_end = menu_entries_get_size(); - - end = ((old_start + rgui_term_layout.height) <= (entries_end)) ? - old_start + rgui_term_layout.height : entries_end; - /* Render background */ rgui_render_background(); @@ -3295,7 +3367,7 @@ static void rgui_render(void *data, * normal menu thumbnail/text list display modes */ if (current_display_cb) rgui_render_osk(rgui, &ticker, &ticker_smooth, use_smooth_ticker); - else if (rgui->show_fs_thumbnail && rgui->entry_has_thumbnail && (fs_thumbnail.is_valid || (rgui->thumbnail_queue_size > 0))) + else if (show_fs_thumbnail) { /* If fullscreen thumbnails are enabled and we are viewing a playlist, * switch to fullscreen thumbnail view mode if either current thumbnail @@ -3787,7 +3859,7 @@ static void rgui_render(void *data, !video_driver_has_windowed(); if (settings->bools.menu_mouse_enable && cursor_visible) - rgui_blit_cursor(); + rgui_blit_cursor(rgui); } } @@ -4226,6 +4298,7 @@ static void *rgui_init(void **userdata, bool video_is_threaded) start = 0; menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &start); + rgui->scroll_y = 0; rgui_init_font_lut(); @@ -4692,7 +4765,10 @@ static void rgui_navigation_set(void *data, bool scroll) } if (do_set_start) + { menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &start); + rgui->scroll_y = start * FONT_HEIGHT_STRIDE; + } } static void rgui_navigation_set_last(void *data) @@ -4782,27 +4858,51 @@ static int rgui_environ(enum menu_environ_cb type, return -1; } -static int rgui_pointer_tap(void *data, +static int rgui_pointer_up(void *data, unsigned x, unsigned y, unsigned ptr, menu_file_list_cbs_t *cbs, menu_entry_t *entry, unsigned action) { + rgui_t *rgui = (rgui_t*)data; unsigned header_height = menu_display_get_header_height(); + size_t selection = menu_navigation_get_selection(); + bool show_fs_thumbnail = false; - if (y < header_height) - { - size_t selection = menu_navigation_get_selection(); - return menu_entry_action(entry, (unsigned)selection, MENU_ACTION_CANCEL); - } - else if (ptr <= (menu_entries_get_size() - 1)) - { - size_t selection = menu_navigation_get_selection(); + if (!rgui) + return -1; - if (ptr == selection && cbs && cbs->action_select) + show_fs_thumbnail = + rgui->show_fs_thumbnail && + rgui->entry_has_thumbnail && + (fs_thumbnail.is_valid || (rgui->thumbnail_queue_size > 0)); + + if (show_fs_thumbnail) + { + /* If we are currently showing a fullscreen thumbnail: + * - Must provide a mechanism for toggling it off + * - A normal mouse press should just select the current + * entry (for which the thumbnail is being shown) */ + if (y < header_height) + rgui_update_thumbnail_image(rgui); + else return menu_entry_action(entry, (unsigned)selection, MENU_ACTION_SELECT); + } + else + { + if (y < header_height) + return menu_entry_action(entry, (unsigned)selection, MENU_ACTION_CANCEL); + else if (ptr <= (menu_entries_get_size() - 1)) + { + /* If currently selected item matches 'pointer' value, + * perform a MENU_ACTION_SELECT on it */ + if (ptr == selection) + return menu_entry_action(entry, (unsigned)selection, MENU_ACTION_SELECT); - menu_navigation_set_selection(ptr); - menu_driver_navigation_set(false); + /* Otherwise, just move the current selection to the + * 'pointer' value */ + menu_navigation_set_selection(ptr); + menu_driver_navigation_set(false); + } } return 0; @@ -5065,17 +5165,16 @@ menu_ctx_driver_t menu_ctx_rgui = { rgui_load_image, "rgui", rgui_environ, - rgui_pointer_tap, NULL, /* update_thumbnail_path */ rgui_update_thumbnail_image, rgui_refresh_thumbnail_image, rgui_set_thumbnail_system, rgui_get_thumbnail_system, NULL, /* set_thumbnail_content */ - NULL, /* osk_ptr_at_pos */ + rgui_osk_ptr_at_pos, NULL, /* update_savestate_thumbnail_path */ NULL, /* update_savestate_thumbnail_image */ NULL, /* pointer_down */ - NULL, /* pointer_up */ + rgui_pointer_up, /* pointer_up */ NULL, /* get_load_content_animation_data */ }; diff --git a/menu/drivers/stripes.c b/menu/drivers/stripes.c index 2284c393e0..1aaefbd210 100644 --- a/menu/drivers/stripes.c +++ b/menu/drivers/stripes.c @@ -2655,23 +2655,25 @@ static void stripes_render(void *data, bool is_idle) { size_t i; + menu_input_pointer_t pointer; settings_t *settings = config_get_ptr(); stripes_handle_t *stripes = (stripes_handle_t*)data; unsigned end = (unsigned)menu_entries_get_size(); - bool mouse_enable = settings->bools.menu_mouse_enable; - bool pointer_enable = settings->bools.menu_pointer_enable; if (!stripes) return; - if (pointer_enable || mouse_enable) + menu_input_get_pointer_state(&pointer); + + if (pointer.type != MENU_POINTER_DISABLED) { size_t selection = menu_navigation_get_selection(); - int16_t pointer_y = menu_input_pointer_state(MENU_POINTER_Y_AXIS); - int16_t mouse_y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS) - + (stripes->cursor_size/2); + int16_t pointer_y = pointer.y; unsigned first = 0, last = end; + pointer_y = (pointer.type == MENU_POINTER_MOUSE) ? + pointer_y + (stripes->cursor_size/2) : pointer_y; + if (height) stripes_calculate_visible_range(stripes, height, end, selection, &first, &last); @@ -2682,17 +2684,8 @@ static void stripes_render(void *data, + stripes_item_y(stripes, (int)i, selection); float item_y2 = item_y1 + stripes->icon_size; - if (pointer_enable) - { - if (pointer_y > item_y1 && pointer_y < item_y2) - menu_input_ctl(MENU_INPUT_CTL_POINTER_PTR, &i); - } - - if (mouse_enable) - { - if (mouse_y > item_y1 && mouse_y < item_y2) - menu_input_ctl(MENU_INPUT_CTL_MOUSE_PTR, &i); - } + if (pointer_y > item_y1 && pointer_y < item_y2) + menu_input_set_pointer_selection(i); } } @@ -3005,14 +2998,17 @@ static void stripes_frame(void *data, video_frame_info_t *video_info) /* Cursor image */ if (stripes->mouse_show) { + menu_input_pointer_t pointer; + menu_input_get_pointer_state(&pointer); + menu_display_set_alpha(stripes_coord_white, MIN(stripes->alpha, 1.00f)); menu_display_draw_cursor( video_info, &stripes_coord_white[0], stripes->cursor_size, stripes->textures.list[STRIPES_TEXTURE_POINTER], - menu_input_mouse_state(MENU_MOUSE_X_AXIS), - menu_input_mouse_state(MENU_MOUSE_Y_AXIS), + pointer.x, + pointer.y, width, height); } @@ -4390,7 +4386,7 @@ error: return false; } -static int stripes_pointer_tap(void *userdata, +static int stripes_pointer_up(void *userdata, unsigned x, unsigned y, unsigned ptr, menu_file_list_cbs_t *cbs, menu_entry_t *entry, unsigned action) @@ -4449,7 +4445,6 @@ menu_ctx_driver_t menu_ctx_stripes = { stripes_load_image, "stripes", stripes_environ, - stripes_pointer_tap, stripes_update_thumbnail_path, stripes_update_thumbnail_image, stripes_refresh_thumbnail_image, @@ -4460,6 +4455,6 @@ menu_ctx_driver_t menu_ctx_stripes = { stripes_update_savestate_thumbnail_path, stripes_update_savestate_thumbnail_image, NULL, /* pointer_down */ - NULL, /* pointer_up */ + stripes_pointer_up, /* pointer_up */ NULL /* get_load_content_animation_data */ }; diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index 389234a303..5ab7814f2a 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -3328,8 +3328,8 @@ static void xmb_render(void *data, unsigned end = (unsigned)menu_entries_get_size(); bool mouse_enable = settings->bools.menu_mouse_enable; bool pointer_enable = settings->bools.menu_pointer_enable; - float scale_factor; + menu_input_pointer_t pointer; if (!xmb) return; @@ -3342,14 +3342,17 @@ static void xmb_render(void *data, xmb->previous_scale_factor = scale_factor; - if (pointer_enable || mouse_enable) + menu_input_get_pointer_state(&pointer); + + if (pointer.type != MENU_POINTER_DISABLED) { size_t selection = menu_navigation_get_selection(); - int16_t pointer_y = menu_input_pointer_state(MENU_POINTER_Y_AXIS); - int16_t mouse_y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS) - + (xmb->cursor_size/2); + int16_t pointer_y = pointer.y; unsigned first = 0, last = end; + pointer_y = (pointer.type == MENU_POINTER_MOUSE) ? + pointer_y + (xmb->cursor_size/2) : pointer_y; + if (height) xmb_calculate_visible_range(xmb, height, end, (unsigned)selection, &first, &last); @@ -3360,17 +3363,8 @@ static void xmb_render(void *data, + xmb_item_y(xmb, (int)i, selection); float item_y2 = item_y1 + xmb->icon_size; - if (pointer_enable) - { - if (pointer_y > item_y1 && pointer_y < item_y2) - menu_input_ctl(MENU_INPUT_CTL_POINTER_PTR, &i); - } - - if (mouse_enable) - { - if (mouse_y > item_y1 && mouse_y < item_y2) - menu_input_ctl(MENU_INPUT_CTL_MOUSE_PTR, &i); - } + if (pointer_y > item_y1 && pointer_y < item_y2) + menu_input_set_pointer_selection(i); } } @@ -4324,14 +4318,17 @@ static void xmb_frame(void *data, video_frame_info_t *video_info) /* Cursor image */ if (xmb->mouse_show) { + menu_input_pointer_t pointer; + menu_input_get_pointer_state(&pointer); + menu_display_set_alpha(coord_white, MIN(xmb->alpha, 1.00f)); menu_display_draw_cursor( video_info, &coord_white[0], xmb->cursor_size, xmb->textures.list[XMB_TEXTURE_POINTER], - menu_input_mouse_state(MENU_MOUSE_X_AXIS), - menu_input_mouse_state(MENU_MOUSE_Y_AXIS), + pointer.x, + pointer.y, width, height); } @@ -5979,7 +5976,7 @@ error: return false; } -static int xmb_pointer_tap(void *userdata, +static int xmb_pointer_up(void *userdata, unsigned x, unsigned y, unsigned ptr, menu_file_list_cbs_t *cbs, menu_entry_t *entry, unsigned action) @@ -5994,7 +5991,7 @@ static int xmb_pointer_tap(void *userdata, else if (ptr <= (menu_entries_get_size() - 1)) { size_t selection = menu_navigation_get_selection(); - if (ptr == selection && cbs && cbs->action_select) + if (ptr == selection) return (unsigned)menu_entry_action(entry, (unsigned)selection, MENU_ACTION_SELECT); menu_navigation_set_selection(ptr); @@ -6060,7 +6057,6 @@ menu_ctx_driver_t menu_ctx_xmb = { xmb_load_image, "xmb", xmb_environ, - xmb_pointer_tap, xmb_update_thumbnail_path, xmb_update_thumbnail_image, xmb_refresh_thumbnail_image, @@ -6071,7 +6067,7 @@ menu_ctx_driver_t menu_ctx_xmb = { xmb_update_savestate_thumbnail_path, xmb_update_savestate_thumbnail_image, NULL, /* pointer_down */ - NULL, /* pointer_up */ + xmb_pointer_up, #ifdef HAVE_MENU_WIDGETS xmb_get_load_content_animation_data #else diff --git a/menu/drivers/xui.cpp b/menu/drivers/xui.cpp index d3eb745661..e2b20138d1 100644 --- a/menu/drivers/xui.cpp +++ b/menu/drivers/xui.cpp @@ -721,7 +721,6 @@ menu_ctx_driver_t menu_ctx_xui = { NULL, /* load_image */ "xui", xui_environ, - NULL, /* pointer_tap */ NULL, /* update_thumbnail_path */ NULL, /* update_thumbnail_image */ NULL, /* refresh_thumbnail_image */ diff --git a/menu/menu_defines.h b/menu/menu_defines.h index 47de53f339..cf0b5103db 100644 --- a/menu/menu_defines.h +++ b/menu/menu_defines.h @@ -66,7 +66,6 @@ enum rarch_menu_ctl_state RARCH_MENU_CTL_FIND_DRIVER, RARCH_MENU_CTL_LIST_FREE, RARCH_MENU_CTL_ENVIRONMENT, - RARCH_MENU_CTL_POINTER_TAP, RARCH_MENU_CTL_POINTER_DOWN, RARCH_MENU_CTL_POINTER_UP, RARCH_MENU_CTL_OSK_PTR_AT_POS, @@ -305,38 +304,6 @@ enum menu_action MENU_ACTION_POINTER_PRESSED }; -enum menu_input_pointer_state -{ - MENU_POINTER_X_AXIS = 0, - MENU_POINTER_Y_AXIS, - MENU_POINTER_DELTA_X_AXIS, - MENU_POINTER_DELTA_Y_AXIS, - MENU_POINTER_PRESSED -}; - -enum menu_input_mouse_state -{ - MENU_MOUSE_X_AXIS = 0, - MENU_MOUSE_Y_AXIS, - MENU_MOUSE_LEFT_BUTTON, - MENU_MOUSE_RIGHT_BUTTON, - MENU_MOUSE_WHEEL_UP, - MENU_MOUSE_WHEEL_DOWN, - MENU_MOUSE_HORIZ_WHEEL_UP, - MENU_MOUSE_HORIZ_WHEEL_DOWN -}; - -enum menu_input_ctl_state -{ - MENU_INPUT_CTL_NONE = 0, - MENU_INPUT_CTL_MOUSE_PTR, - MENU_INPUT_CTL_POINTER_PTR, - MENU_INPUT_CTL_POINTER_ACCEL_READ, - MENU_INPUT_CTL_POINTER_ACCEL_WRITE, - MENU_INPUT_CTL_IS_POINTER_DRAGGED, - MENU_INPUT_CTL_DEINIT -}; - enum playlist_sublabel_runtime { PLAYLIST_RUNTIME_PER_CORE = 0, diff --git a/menu/menu_driver.c b/menu/menu_driver.c index 168a2aaaaf..881fc8ed60 100644 --- a/menu/menu_driver.c +++ b/menu/menu_driver.c @@ -2810,12 +2810,12 @@ void menu_display_snow(int width, int height) if (p->alive) { - int16_t mouse_x = menu_input_mouse_state( - MENU_MOUSE_X_AXIS); + menu_input_pointer_t pointer; + menu_input_get_pointer_state(&pointer); p->y += p->yspeed; p->x += menu_display_scalef( - mouse_x, 0, width, -0.3, 0.3); + pointer.x, 0, width, -0.3, 0.3); p->x += p->xspeed; p->alive = p->y >= 0 && p->y < height @@ -3097,6 +3097,10 @@ static bool menu_init(menu_handle_t *menu_data) { settings_t *settings = config_get_ptr(); + /* Ensure that menu pointer input is correctly + * initialised */ + menu_input_reset(); + if (!menu_entries_init()) return false; @@ -3463,7 +3467,7 @@ bool menu_driver_ctl(enum rarch_menu_ctl_state state, void *data) for (i = 0; i < SCROLL_INDEX_SIZE; i++) scroll_index_list[i] = 0; - menu_input_ctl(MENU_INPUT_CTL_DEINIT, NULL); + menu_input_reset(); if (menu_driver_ctx && menu_driver_ctx->free) menu_driver_ctx->free(menu_userdata); @@ -3538,19 +3542,6 @@ bool menu_driver_ctl(enum rarch_menu_ctl_state state, void *data) } } return false; - case RARCH_MENU_CTL_POINTER_TAP: - { - menu_ctx_pointer_t *point = (menu_ctx_pointer_t*)data; - if (!menu_driver_ctx || !menu_driver_ctx->pointer_tap) - { - point->retcode = 0; - return false; - } - point->retcode = menu_driver_ctx->pointer_tap(menu_userdata, - point->x, point->y, point->ptr, - point->cbs, point->entry, point->action); - } - break; case RARCH_MENU_CTL_POINTER_DOWN: { menu_ctx_pointer_t *point = (menu_ctx_pointer_t*)data; diff --git a/menu/menu_driver.h b/menu/menu_driver.h index 8e11b61b0a..07c7691c38 100644 --- a/menu/menu_driver.h +++ b/menu/menu_driver.h @@ -305,9 +305,6 @@ typedef struct menu_ctx_driver bool (*load_image)(void *userdata, void *data, enum menu_image_type type); const char *ident; int (*environ_cb)(enum menu_environ_cb type, void *data, void *userdata); - int (*pointer_tap)(void *data, unsigned x, unsigned y, unsigned ptr, - menu_file_list_cbs_t *cbs, - menu_entry_t *entry, unsigned action); void (*update_thumbnail_path)(void *data, unsigned i, char pos); void (*update_thumbnail_image)(void *data); void (*refresh_thumbnail_image)(void *data); diff --git a/menu/menu_input.h b/menu/menu_input.h index f613b8c41e..8b8899be79 100644 --- a/menu/menu_input.h +++ b/menu/menu_input.h @@ -29,25 +29,66 @@ RETRO_BEGIN_DECLS +#define MENU_INPUT_HIDE_CURSOR_DELAY 4000000 /* 4 seconds */ + +#define MENU_INPUT_PRESS_TIME_SHORT 250000 /* 250 ms */ +#define MENU_INPUT_PRESS_TIME_LONG 1500000 /* 1.5 second */ +/* (Anthing less than 'short' is considered a tap) */ + +#define MENU_INPUT_Y_ACCEL_DECAY_FACTOR 0.96f + +enum menu_pointer_type +{ + MENU_POINTER_DISABLED = 0, + MENU_POINTER_MOUSE, + MENU_POINTER_TOUCHSCREEN +}; + +enum menu_input_mouse_hw_id +{ + MENU_MOUSE_X_AXIS = 0, + MENU_MOUSE_Y_AXIS, + MENU_MOUSE_LEFT_BUTTON, + MENU_MOUSE_RIGHT_BUTTON, + MENU_MOUSE_WHEEL_UP, + MENU_MOUSE_WHEEL_DOWN, + MENU_MOUSE_HORIZ_WHEEL_UP, + MENU_MOUSE_HORIZ_WHEEL_DOWN +}; + +/* Defines set of (abstracted) inputs/states + * common to mouse + touchscreen hardware */ +typedef struct menu_input_pointer_hw_state +{ + bool active; + int16_t x; + int16_t y; + bool select_pressed; + bool cancel_pressed; + bool up_pressed; + bool down_pressed; + bool left_pressed; + bool right_pressed; +} menu_input_pointer_hw_state_t; + +typedef struct menu_input_pointer +{ + enum menu_pointer_type type; + bool pressed; + bool dragged; + retro_time_t press_duration; + int16_t x; + int16_t y; + int16_t dx; + int16_t dy; + float y_accel; +} menu_input_pointer_t; + typedef struct menu_input { - struct - { - unsigned ptr; - } mouse; - - struct - { - bool back; - bool pressed[2]; - int16_t x; - int16_t y; - int16_t dx; - int16_t dy; - unsigned ptr; - unsigned counter; - float accel; - } pointer; + menu_input_pointer_t pointer; + unsigned ptr; + bool select_inhibit; } menu_input_t; typedef struct menu_input_ctx_hitbox @@ -58,16 +99,32 @@ typedef struct menu_input_ctx_hitbox int32_t y2; } menu_input_ctx_hitbox_t; +/* Must be called inside menu_driver_toggle() + * Prevents phantom input when using an overlay to + * toggle menu ON if overlays are disabled in-menu */ +void menu_input_driver_toggle(bool on); + +/* Provides access to all pointer device parameters */ +void menu_input_get_pointer_state(menu_input_pointer_t *pointer); + +/* Getters/setters for menu item (index) currently + * selected/highlighted (hovered over) by the pointer + * device + * Note: Each menu driver is responsible for setting this */ +unsigned menu_input_get_pointer_selection(void); +void menu_input_set_pointer_selection(unsigned selection); + +/* Allows pointer y acceleration to be overridden + * (typically want to set acceleration to zero when + * calling populate entries) */ +void menu_input_set_pointer_y_accel(float y_accel); + +void menu_input_reset(void); + +bool menu_input_pointer_check_vector_inside_hitbox(menu_input_ctx_hitbox_t *hitbox); + void menu_input_post_iterate(int *ret, unsigned action); -int16_t menu_input_pointer_state(enum menu_input_pointer_state state); - -int16_t menu_input_mouse_state(enum menu_input_mouse_state state); - -bool menu_input_mouse_check_vector_inside_hitbox(menu_input_ctx_hitbox_t *hitbox); - -bool menu_input_ctl(enum menu_input_ctl_state state, void *data); - RETRO_END_DECLS #endif diff --git a/retroarch.c b/retroarch.c index c3e40ff826..04b0dc902d 100644 --- a/retroarch.c +++ b/retroarch.c @@ -945,27 +945,16 @@ static char error_string[255] = {0}; #ifdef HAVE_MENU /* MENU INPUT GLOBAL VARIABLES */ -enum menu_mouse_action -{ - MENU_MOUSE_ACTION_NONE = 0, - MENU_MOUSE_ACTION_BUTTON_L, - MENU_MOUSE_ACTION_BUTTON_L_TOGGLE, - MENU_MOUSE_ACTION_BUTTON_L_SET_NAVIGATION, - MENU_MOUSE_ACTION_BUTTON_R, - MENU_MOUSE_ACTION_WHEEL_UP, - MENU_MOUSE_ACTION_WHEEL_DOWN, - MENU_MOUSE_ACTION_HORIZ_WHEEL_UP, - MENU_MOUSE_ACTION_HORIZ_WHEEL_DOWN -}; - -static const char **menu_input_dialog_keyboard_buffer = {NULL}; -static unsigned menu_input_dialog_keyboard_type = 0; -static unsigned menu_input_dialog_keyboard_idx = 0; -static char menu_input_dialog_keyboard_label_setting[256] = {0}; -static char menu_input_dialog_keyboard_label[256] = {0}; -static bool menu_input_dialog_keyboard_display = false; -static unsigned char menu_keyboard_key_state[RETROK_LAST] = {0}; +static const char **menu_input_dialog_keyboard_buffer = {NULL}; +static unsigned menu_input_dialog_keyboard_type = 0; +static unsigned menu_input_dialog_keyboard_idx = 0; +static char menu_input_dialog_keyboard_label_setting[256] = {0}; +static char menu_input_dialog_keyboard_label[256] = {0}; +static bool menu_input_dialog_keyboard_display = false; +static unsigned char menu_keyboard_key_state[RETROK_LAST] = {0}; +/* Since these are static/global, they are initialised to zero */ +static menu_input_pointer_hw_state_t menu_input_pointer_hw_state; static menu_input_t menu_input_state; /* Is the menu driver still running? */ @@ -12279,13 +12268,306 @@ static int16_t input_joypad_axis(const input_device_driver_t *drv, /* MENU INPUT */ #ifdef HAVE_MENU + +void menu_input_driver_toggle(bool on) +{ + menu_input_t *menu_input = &menu_input_state; +#ifdef HAVE_OVERLAY + settings_t *settings = configuration_settings; + + if (on) + { + /* If an overlay was displayed before the toggle + * and overlays are disabled in menu, need to + * inhibit 'select' input */ + if (settings->bools.input_overlay_hide_in_menu) + if (settings->bools.input_overlay_enable && overlay_ptr && overlay_ptr->alive) + menu_input->select_inhibit = true; + } + else + menu_input->select_inhibit = false; +#else + menu_input->select_inhibit = false; +#endif +} + +int16_t menu_input_read_mouse_hw(enum menu_input_mouse_hw_id id) +{ + rarch_joypad_info_t joypad_info; + unsigned type = 0; + unsigned device = RETRO_DEVICE_MOUSE; + + joypad_info.joy_idx = 0; + joypad_info.auto_binds = NULL; + joypad_info.axis_threshold = 0.0f; + + switch (id) + { + case MENU_MOUSE_X_AXIS: + device = RARCH_DEVICE_MOUSE_SCREEN; + type = RETRO_DEVICE_ID_MOUSE_X; + break; + case MENU_MOUSE_Y_AXIS: + device = RARCH_DEVICE_MOUSE_SCREEN; + type = RETRO_DEVICE_ID_MOUSE_Y; + break; + case MENU_MOUSE_LEFT_BUTTON: + type = RETRO_DEVICE_ID_MOUSE_LEFT; + break; + case MENU_MOUSE_RIGHT_BUTTON: + type = RETRO_DEVICE_ID_MOUSE_RIGHT; + break; + case MENU_MOUSE_WHEEL_UP: + type = RETRO_DEVICE_ID_MOUSE_WHEELUP; + break; + case MENU_MOUSE_WHEEL_DOWN: + type = RETRO_DEVICE_ID_MOUSE_WHEELDOWN; + break; + case MENU_MOUSE_HORIZ_WHEEL_UP: + type = RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELUP; + break; + case MENU_MOUSE_HORIZ_WHEEL_DOWN: + type = RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN; + break; + } + + return current_input->input_state(current_input_data, joypad_info, + NULL, 0, device, 0, type); +} + +static void menu_input_get_mouse_hw_state(menu_input_pointer_hw_state_t *hw_state) +{ + settings_t *settings = configuration_settings; + static int16_t last_x = 0; + static int16_t last_y = 0; + static bool last_select_pressed = false; + static bool last_cancel_pressed = false; + bool overlay_active = false; + bool mouse_enabled = settings->bools.menu_mouse_enable; + bool is_rgui = string_is_equal(settings->arrays.menu_driver, "rgui"); + + /* Easiest to set inactive by default, and toggle + * when input is detected */ + hw_state->active = false; + +#ifdef HAVE_OVERLAY + /* Menu pointer controls are ignored when overlays are enabled. */ + overlay_active = settings->bools.input_overlay_enable && overlay_ptr && overlay_ptr->alive; + if (overlay_active) + mouse_enabled = false; +#endif + + if (!mouse_enabled) + { + hw_state->x = 0; + hw_state->y = 0; + hw_state->select_pressed = false; + hw_state->cancel_pressed = false; + hw_state->up_pressed = false; + hw_state->down_pressed = false; + hw_state->left_pressed = false; + hw_state->right_pressed = false; + return; + } + + /* X pos */ + hw_state->x = menu_input_read_mouse_hw(MENU_MOUSE_X_AXIS); + if (hw_state->x != last_x) + hw_state->active = true; + last_x = hw_state->x; + + /* Y pos */ + hw_state->y = menu_input_read_mouse_hw(MENU_MOUSE_Y_AXIS); + if (hw_state->y != last_y) + hw_state->active = true; + last_y = hw_state->y; + + /* > X/Y adjustment */ + if (is_rgui) + { + /* RGUI uses a framebuffer texture + custom viewports, + * which means we have to convert from screen space to + * menu space... */ + size_t fb_pitch; + unsigned fb_width, fb_height; + struct video_viewport vp = {0}; + + /* Read display/framebuffer info */ + menu_display_get_fb_size(&fb_width, &fb_height, &fb_pitch); + video_driver_get_viewport_info(&vp); + + /* Adjust X pos */ + hw_state->x = (int16_t)(((float)(hw_state->x - vp.x) / (float)vp.width) * (float)fb_width); + hw_state->x = hw_state->x < 0 ? 0 : hw_state->x; + hw_state->x = hw_state->x >= fb_width ? fb_width - 1 : hw_state->x; + + /* Adjust Y pos */ + hw_state->y = (int16_t)(((float)(hw_state->y - vp.y) / (float)vp.height) * (float)fb_height); + hw_state->y = hw_state->y < 0 ? 0 : hw_state->y; + hw_state->y = hw_state->y >= fb_height ? fb_height - 1 : hw_state->y; + } + + /* Select (LMB) + * Note that releasing select also counts as activity */ + hw_state->select_pressed = (bool)menu_input_read_mouse_hw(MENU_MOUSE_LEFT_BUTTON); + if (hw_state->select_pressed || (hw_state->select_pressed != last_select_pressed)) + hw_state->active = true; + last_select_pressed = hw_state->select_pressed; + + /* Cancel (RMB) + * Note that releasing cancel also counts as activity */ + hw_state->cancel_pressed = (bool)menu_input_read_mouse_hw(MENU_MOUSE_RIGHT_BUTTON); + if (hw_state->cancel_pressed || (hw_state->cancel_pressed != last_cancel_pressed)) + hw_state->active = true; + last_cancel_pressed = hw_state->cancel_pressed; + + /* Up (mouse wheel up) */ + hw_state->up_pressed = (bool)menu_input_read_mouse_hw(MENU_MOUSE_WHEEL_UP); + if (hw_state->up_pressed) + hw_state->active = true; + + /* Down (mouse wheel down) */ + hw_state->down_pressed = (bool)menu_input_read_mouse_hw(MENU_MOUSE_WHEEL_DOWN); + if (hw_state->down_pressed) + hw_state->active = true; + + /* Left (mouse wheel horizontal left) */ + hw_state->left_pressed = (bool)menu_input_read_mouse_hw(MENU_MOUSE_HORIZ_WHEEL_DOWN); + if (hw_state->left_pressed) + hw_state->active = true; + + /* Right (mouse wheel horizontal right) */ + hw_state->right_pressed = (bool)menu_input_read_mouse_hw(MENU_MOUSE_HORIZ_WHEEL_UP); + if (hw_state->right_pressed) + hw_state->active = true; +} + +static void menu_input_get_touchscreen_hw_state(menu_input_pointer_hw_state_t *hw_state) +{ + rarch_joypad_info_t joypad_info; + int pointer_x, pointer_y; + size_t fb_pitch; + unsigned fb_width, fb_height; + settings_t *settings = configuration_settings; + const struct retro_keybind *binds[MAX_USERS] = {NULL}; + menu_handle_t *menu_data = menu_driver_get_ptr(); + /* Is a background texture set for the current menu driver? + * Checks if the menu framebuffer is set. + * This would usually only return true + * for framebuffer-based menu drivers, like RGUI. */ + int pointer_device = + (menu_data && menu_data->driver_ctx && menu_data->driver_ctx->set_texture) ? + RETRO_DEVICE_POINTER : RARCH_DEVICE_POINTER_SCREEN; + static int16_t last_x = 0; + static int16_t last_y = 0; + static bool last_select_pressed = false; + static bool last_cancel_pressed = false; + bool overlay_active = false; + bool pointer_enabled = settings->bools.menu_pointer_enable; + + /* Easiest to set inactive by default, and toggle + * when input is detected */ + hw_state->active = false; + + /* Touch screens don't have mouse wheels, so these + * are always disabled */ + hw_state->up_pressed = false; + hw_state->down_pressed = false; + hw_state->left_pressed = false; + hw_state->right_pressed = false; + +#ifdef HAVE_OVERLAY + /* Menu pointer controls are ignored when overlays are enabled. */ + overlay_active = settings->bools.input_overlay_enable && overlay_ptr && overlay_ptr->alive; + if (overlay_active) + pointer_enabled = false; +#endif + + /* If touchscreen is disabled, ignore all input */ + if (!pointer_enabled) + { + hw_state->x = 0; + hw_state->y = 0; + hw_state->select_pressed = false; + hw_state->cancel_pressed = false; + return; + } + + menu_display_get_fb_size(&fb_width, &fb_height, &fb_pitch); + + joypad_info.joy_idx = 0; + joypad_info.auto_binds = NULL; + joypad_info.axis_threshold = 0.0f; + + /* X pos */ + pointer_x = current_input->input_state( + current_input_data, joypad_info, binds, + 0, pointer_device, 0, RETRO_DEVICE_ID_POINTER_X); + hw_state->x = ((pointer_x + 0x7fff) * (int)fb_width) / 0xFFFF; + + /* > An annoyance - we get different starting positions + * depending upon whether pointer_device is + * RETRO_DEVICE_POINTER or RARCH_DEVICE_POINTER_SCREEN, + * so different 'activity' checks are required to prevent + * false positives on first run */ + if (pointer_device == RARCH_DEVICE_POINTER_SCREEN) + { + if (hw_state->x != last_x) + hw_state->active = true; + last_x = hw_state->x; + } + else + { + if (pointer_x != last_x) + hw_state->active = true; + last_x = pointer_x; + } + + /* Y pos */ + pointer_y = current_input->input_state( + current_input_data, joypad_info, binds, + 0, pointer_device, 0, RETRO_DEVICE_ID_POINTER_Y); + hw_state->y = ((pointer_y + 0x7fff) * (int)fb_height) / 0xFFFF; + + if (pointer_device == RARCH_DEVICE_POINTER_SCREEN) + { + if (hw_state->y != last_y) + hw_state->active = true; + last_y = hw_state->y; + } + else + { + if (pointer_y != last_y) + hw_state->active = true; + last_y = pointer_y; + } + + /* Select (touch screen contact) + * Note that releasing select also counts as activity */ + hw_state->select_pressed = (bool)current_input->input_state( + current_input_data, joypad_info, binds, + 0, pointer_device, 0, RETRO_DEVICE_ID_POINTER_PRESSED); + if (hw_state->select_pressed || (hw_state->select_pressed != last_select_pressed)) + hw_state->active = true; + last_select_pressed = hw_state->select_pressed; + + /* Cancel (touch screen 'back' - don't know what is this, but whatever...) + * Note that releasing cancel also counts as activity */ + hw_state->cancel_pressed = (bool)current_input->input_state( + current_input_data, joypad_info, binds, + 0, pointer_device, 0, RARCH_DEVICE_ID_POINTER_BACK); + if (hw_state->cancel_pressed || (hw_state->cancel_pressed != last_cancel_pressed)) + hw_state->active = true; + last_cancel_pressed = hw_state->cancel_pressed; +} + /* * This function gets called in order to process all input events * for the current frame. * * Sends input code to menu for one frame. * - * It uses as input the local variables' input' and 'trigger_input'. + * It uses as input the local variables 'input' and 'trigger_input'. * * Mouse and touch input events get processed inside this function. * @@ -12304,30 +12586,30 @@ static unsigned menu_event( bool display_kb) { /* Used for key repeat */ - static float delay_timer = 0.0f; - static float delay_count = 0.0f; - static bool initial_held = true; - static bool first_held = false; - static unsigned ok_old = 0; - unsigned ret = MENU_ACTION_NOOP; - bool set_scroll = false; - bool mouse_enabled = false; - size_t new_scroll_accel = 0; - menu_input_t *menu_input = &menu_input_state; - settings_t *settings = configuration_settings; - bool swap_ok_cancel_btns = settings->bools.input_menu_swap_ok_cancel_buttons; - bool input_swap_override = input_autoconfigure_get_swap_override(); - unsigned menu_ok_btn = (!input_swap_override && - swap_ok_cancel_btns) ? - RETRO_DEVICE_ID_JOYPAD_B : RETRO_DEVICE_ID_JOYPAD_A; - unsigned menu_cancel_btn = (!input_swap_override && - swap_ok_cancel_btns) ? - RETRO_DEVICE_ID_JOYPAD_A : RETRO_DEVICE_ID_JOYPAD_B; - unsigned ok_current = BIT256_GET_PTR(p_input, menu_ok_btn); - unsigned ok_trigger = ok_current & ~ok_old; - bool is_rgui = string_is_equal(settings->arrays.menu_driver, "rgui"); + static float delay_timer = 0.0f; + static float delay_count = 0.0f; + static bool initial_held = true; + static bool first_held = false; + static unsigned ok_old = 0; + unsigned ret = MENU_ACTION_NOOP; + bool set_scroll = false; + size_t new_scroll_accel = 0; + menu_input_t *menu_input = &menu_input_state; + menu_input_pointer_hw_state_t *pointer_hw_state = &menu_input_pointer_hw_state; + settings_t *settings = configuration_settings; + bool swap_ok_cancel_btns = settings->bools.input_menu_swap_ok_cancel_buttons; + bool input_swap_override = input_autoconfigure_get_swap_override(); + unsigned menu_ok_btn = + (!input_swap_override && swap_ok_cancel_btns) ? + RETRO_DEVICE_ID_JOYPAD_B : RETRO_DEVICE_ID_JOYPAD_A; + unsigned menu_cancel_btn = + (!input_swap_override && swap_ok_cancel_btns) ? + RETRO_DEVICE_ID_JOYPAD_A : RETRO_DEVICE_ID_JOYPAD_B; + unsigned ok_current = BIT256_GET_PTR(p_input, menu_ok_btn); + unsigned ok_trigger = ok_current & ~ok_old; + bool is_rgui = string_is_equal(settings->arrays.menu_driver, "rgui"); - ok_old = ok_current; + ok_old = ok_current; if (bits_any_set(p_input->data, ARRAY_SIZE(p_input->data))) { @@ -12476,526 +12758,321 @@ static unsigned menu_event( menu_keyboard_key_state[RETROK_F11] = 0; } - mouse_enabled = settings->bools.menu_mouse_enable; -#ifdef HAVE_OVERLAY - if (!mouse_enabled) - mouse_enabled = !(settings->bools.input_overlay_enable - && overlay_ptr && overlay_ptr->alive); -#endif + /* Get pointer (mouse + touchscreen) input */ - if (!mouse_enabled) - menu_input->mouse.ptr = 0; - - if (settings->bools.menu_pointer_enable) + /* > If pointer input is disabled, do nothing */ + if (!settings->bools.menu_mouse_enable && !settings->bools.menu_pointer_enable) + menu_input->pointer.type = MENU_POINTER_DISABLED; + else { - /* This function gets called for handling pointer events. - * - * Pointer events are touchscreen events that are spawned - * by touchpad/touchscreen. */ - rarch_joypad_info_t joypad_info; - int pointer_x, pointer_y; - size_t fb_pitch; - unsigned fb_width, fb_height; - const struct retro_keybind *binds[MAX_USERS] = {NULL}; - menu_handle_t *menu_data = menu_driver_get_ptr(); - /* Is a background texture set for the current menu driver? - * Checks if the menu framebuffer is set. - * This would usually only return true - * for framebuffer-based menu drivers, like RGUI. */ - int pointer_device = (menu_data - && menu_data->driver_ctx - && menu_data->driver_ctx->set_texture) - ? - RETRO_DEVICE_POINTER : RARCH_DEVICE_POINTER_SCREEN; + menu_input_pointer_hw_state_t mouse_hw_state = {0}; + menu_input_pointer_hw_state_t touchscreen_hw_state = {0}; - menu_display_get_fb_size(&fb_width, &fb_height, - &fb_pitch); + /* Read mouse */ + if (settings->bools.menu_mouse_enable) + menu_input_get_mouse_hw_state(&mouse_hw_state); - joypad_info.joy_idx = 0; - joypad_info.auto_binds = NULL; - joypad_info.axis_threshold = 0.0f; + /* Read touchscreen + * Note: Could forgo this if mouse is currently active, + * but this is 'cleaner' code... (if performance is a + * concern - and it isn't - user can just disable touch + * screen support) */ + if (settings->bools.menu_pointer_enable) + menu_input_get_touchscreen_hw_state(&touchscreen_hw_state); - pointer_x = - current_input->input_state( - current_input_data, joypad_info, binds, - 0, pointer_device, 0, RETRO_DEVICE_ID_POINTER_X); - pointer_y = - current_input->input_state( - current_input_data, joypad_info, binds, - 0, pointer_device, 0, RETRO_DEVICE_ID_POINTER_Y); + /* Mouse takes precedence */ + if (mouse_hw_state.active) + menu_input->pointer.type = MENU_POINTER_MOUSE; + else if (touchscreen_hw_state.active) + menu_input->pointer.type = MENU_POINTER_TOUCHSCREEN; - menu_input->pointer.pressed[0] = current_input->input_state( - current_input_data, joypad_info, binds, - 0, pointer_device, 0, RETRO_DEVICE_ID_POINTER_PRESSED); - menu_input->pointer.pressed[1] = current_input->input_state( - current_input_data, joypad_info, binds, - 0, pointer_device, 1, RETRO_DEVICE_ID_POINTER_PRESSED); - menu_input->pointer.back = current_input->input_state( - current_input_data, joypad_info, binds, - 0, pointer_device, 0, RARCH_DEVICE_ID_POINTER_BACK); + /* Copy input from the current device */ + if (menu_input->pointer.type == MENU_POINTER_MOUSE) + memcpy(pointer_hw_state, &mouse_hw_state, sizeof(menu_input_pointer_hw_state_t)); + else if (menu_input->pointer.type == MENU_POINTER_TOUCHSCREEN) + memcpy(pointer_hw_state, &touchscreen_hw_state, sizeof(menu_input_pointer_hw_state_t)); + } - menu_input->pointer.x = ((pointer_x + 0x7fff) * (int)fb_width) / 0xFFFF; - menu_input->pointer.y = ((pointer_y + 0x7fff) * (int)fb_height) / 0xFFFF; + /* Populate menu_input_state + * Note: dx, dy, ptr, accel entries are set elsewhere */ + if (menu_input->select_inhibit) + { + menu_input->pointer.pressed = false; + menu_input->pointer.x = 0; + menu_input->pointer.y = 0; } else { - menu_input->pointer.x = 0; - menu_input->pointer.y = 0; - menu_input->pointer.dx = 0; - menu_input->pointer.dy = 0; - menu_input->pointer.accel = 0; - menu_input->pointer.pressed[0] = false; - menu_input->pointer.pressed[1] = false; - menu_input->pointer.back = false; - menu_input->pointer.ptr = 0; + menu_input->pointer.pressed = pointer_hw_state->select_pressed; + menu_input->pointer.x = pointer_hw_state->x; + menu_input->pointer.y = pointer_hw_state->y; } return ret; } -bool menu_input_mouse_check_vector_inside_hitbox(menu_input_ctx_hitbox_t *hitbox) +bool menu_input_pointer_check_vector_inside_hitbox(menu_input_ctx_hitbox_t *hitbox) { - int16_t mouse_x = menu_input_mouse_state(MENU_MOUSE_X_AXIS); - int16_t mouse_y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); - bool inside_hitbox = - (mouse_x >= hitbox->x1) - && (mouse_x <= hitbox->x2) - && (mouse_y >= hitbox->y1) - && (mouse_y <= hitbox->y2) - ; + menu_input_pointer_hw_state_t *pointer_hw_state = &menu_input_pointer_hw_state; + int16_t x = pointer_hw_state->x; + int16_t y = pointer_hw_state->y; + bool inside_hitbox = + (x >= hitbox->x1) && + (x <= hitbox->x2) && + (y >= hitbox->y1) && + (y <= hitbox->y2); return inside_hitbox; } -static bool pointer_dragging = false; - -bool menu_input_ctl(enum menu_input_ctl_state state, void *data) +void menu_input_get_pointer_state(menu_input_pointer_t *pointer) { - menu_input_t *menu_input = &menu_input_state; - - if (!menu_input) - return false; - - switch (state) - { - case MENU_INPUT_CTL_DEINIT: - memset(menu_input, 0, sizeof(menu_input_t)); - pointer_dragging = false; - break; - case MENU_INPUT_CTL_MOUSE_PTR: - menu_input->mouse.ptr = (*(unsigned*)data); - break; - case MENU_INPUT_CTL_POINTER_PTR: - menu_input->pointer.ptr = (*(unsigned*)data); - break; - case MENU_INPUT_CTL_POINTER_ACCEL_READ: - { - float *ptr = (float*)data; - *ptr = menu_input->pointer.accel; - } - break; - case MENU_INPUT_CTL_POINTER_ACCEL_WRITE: - menu_input->pointer.accel = (*(float*)data); - break; - case MENU_INPUT_CTL_IS_POINTER_DRAGGED: - return pointer_dragging; - case MENU_INPUT_CTL_NONE: - break; - } - - return true; -} - -static int menu_input_mouse_post_iterate(uint64_t *input_mouse, - menu_file_list_cbs_t *cbs, unsigned action, bool *mouse_activity) -{ - settings_t *settings = configuration_settings; - static bool mouse_oldleft = false; - static bool mouse_oldright = false; - - if ( - !settings->bools.menu_mouse_enable -#ifdef HAVE_OVERLAY - || (settings->bools.input_overlay_enable && overlay_ptr && overlay_ptr->alive) -#endif - ) - { - /* HACK: Need to lie to avoid false hits if mouse is held - * when entering the RetroArch window. */ - - /* This happens if, for example, someone double clicks the - * window border to maximize it. - * - * The proper fix is, of course, triggering on WM_LBUTTONDOWN - * rather than this state change. */ - mouse_oldleft = true; - mouse_oldright = true; - return 0; - } - - if (menu_input_mouse_state(MENU_MOUSE_LEFT_BUTTON)) - { - if (!mouse_oldleft) - { - menu_input_t *menu_input = &menu_input_state; - size_t selection = menu_navigation_get_selection(); - - BIT64_SET(*input_mouse, MENU_MOUSE_ACTION_BUTTON_L); - - mouse_oldleft = true; - - if ((menu_input->mouse.ptr == selection) && cbs && cbs->action_select) - { - BIT64_SET(*input_mouse, MENU_MOUSE_ACTION_BUTTON_L_TOGGLE); - } - else if (menu_input->mouse.ptr <= (menu_entries_get_size() - 1)) - { - BIT64_SET(*input_mouse, MENU_MOUSE_ACTION_BUTTON_L_SET_NAVIGATION); - } - - *mouse_activity = true; - } - } - else - mouse_oldleft = false; - - if (menu_input_mouse_state(MENU_MOUSE_RIGHT_BUTTON)) - { - if (!mouse_oldright) - { - mouse_oldright = true; - BIT64_SET(*input_mouse, MENU_MOUSE_ACTION_BUTTON_R); - *mouse_activity = true; - } - } - else - mouse_oldright = false; - - if (menu_input_mouse_state(MENU_MOUSE_WHEEL_DOWN)) - { - BIT64_SET(*input_mouse, MENU_MOUSE_ACTION_WHEEL_DOWN); - *mouse_activity = true; - } - - if (menu_input_mouse_state(MENU_MOUSE_WHEEL_UP)) - { - BIT64_SET(*input_mouse, MENU_MOUSE_ACTION_WHEEL_UP); - *mouse_activity = true; - } - - if (menu_input_mouse_state(MENU_MOUSE_HORIZ_WHEEL_DOWN)) - { - BIT64_SET(*input_mouse, MENU_MOUSE_ACTION_HORIZ_WHEEL_DOWN); - *mouse_activity = true; - } - - if (menu_input_mouse_state(MENU_MOUSE_HORIZ_WHEEL_UP)) - { - BIT64_SET(*input_mouse, MENU_MOUSE_ACTION_HORIZ_WHEEL_UP); - *mouse_activity = true; - } - - return 0; -} - -static int menu_input_mouse_frame( - menu_file_list_cbs_t *cbs, menu_entry_t *entry, - unsigned action) -{ - static rarch_timer_t mouse_activity_timer = {0}; - bool mouse_activity = false; - bool no_mouse_activity = false; - uint64_t mouse_state = MENU_MOUSE_ACTION_NONE; - int ret = 0; - settings_t *settings = configuration_settings; menu_input_t *menu_input = &menu_input_state; - bool mouse_enable = settings->bools.menu_mouse_enable; - if (mouse_enable) - ret = menu_input_mouse_post_iterate(&mouse_state, cbs, action, &mouse_activity); + if (!pointer) + return; - if ((settings->bools.menu_pointer_enable || mouse_enable)) + /* Copy parameters from global menu_input_state + * (i.e. don't pass by reference) + * This is a fast operation */ + memcpy(pointer, &menu_input->pointer, sizeof(menu_input_pointer_t)); +} + +unsigned menu_input_get_pointer_selection(void) +{ + menu_input_t *menu_input = &menu_input_state; + return menu_input->ptr; +} + +void menu_input_set_pointer_selection(unsigned selection) +{ + menu_input_t *menu_input = &menu_input_state; + menu_input->ptr = selection; +} + +void menu_input_set_pointer_y_accel(float y_accel) +{ + menu_input_t *menu_input = &menu_input_state; + menu_input->pointer.y_accel = y_accel; +} + +void menu_input_reset(void) +{ + menu_input_t *menu_input = &menu_input_state; + menu_input_pointer_hw_state_t *pointer_hw_state = &menu_input_pointer_hw_state; + + memset(menu_input, 0, sizeof(menu_input_t)); + memset(pointer_hw_state, 0, sizeof(menu_input_pointer_hw_state_t)); +} + +static void menu_input_set_pointer_visibility(retro_time_t current_time) +{ + bool show_cursor = false; + static bool cursor_shown = false; + bool hide_cursor = false; + static bool cursor_hidden = false; + static retro_time_t end_time = 0; + menu_input_t *menu_input = &menu_input_state; + menu_input_pointer_hw_state_t *pointer_hw_state = &menu_input_pointer_hw_state; + + /* Ensure that mouse cursor is hidden when not in use */ + if ((menu_input->pointer.type == MENU_POINTER_MOUSE) && pointer_hw_state->active) + { + if ((current_time > end_time) && !cursor_shown) + show_cursor = true; + + end_time = current_time + MENU_INPUT_HIDE_CURSOR_DELAY; + } + else + { + if ((current_time > end_time) && !cursor_hidden) + hide_cursor = true; + } + + if (show_cursor) + { + menu_ctx_environment_t menu_environ; + menu_environ.type = MENU_ENVIRON_ENABLE_MOUSE_CURSOR; + menu_environ.data = NULL; + + menu_driver_ctl(RARCH_MENU_CTL_ENVIRONMENT, &menu_environ); + cursor_shown = true; + cursor_hidden = false; + } + + if (hide_cursor) + { + menu_ctx_environment_t menu_environ; + menu_environ.type = MENU_ENVIRON_DISABLE_MOUSE_CURSOR; + menu_environ.data = NULL; + + menu_driver_ctl(RARCH_MENU_CTL_ENVIRONMENT, &menu_environ); + cursor_shown = false; + cursor_hidden = true; + } +} + +static int menu_input_pointer_post_iterate( + retro_time_t current_time, + menu_file_list_cbs_t *cbs, + menu_entry_t *entry, unsigned action) +{ + static retro_time_t start_time = 0; + static int16_t start_x = 0; + static int16_t start_y = 0; + static int16_t last_x = 0; + static int16_t last_y = 0; + static bool last_select_pressed = false; + static bool last_cancel_pressed = false; + static bool last_left_pressed = false; + static bool last_right_pressed = false; + bool attenuate_y_accel = true; + bool osk_active = menu_input_dialog_get_display_kb_internal(); + int ret = 0; + menu_input_pointer_hw_state_t *pointer_hw_state = &menu_input_pointer_hw_state; + menu_input_t *menu_input = &menu_input_state; + settings_t *settings = configuration_settings; + + /* If onscreen keyboard is shown and we currently have + * active mouse input, highlight key under mouse cursor */ + if (osk_active && + (menu_input->pointer.type == MENU_POINTER_MOUSE) && + pointer_hw_state->active) { - static unsigned mouse_old_x = 0; - static unsigned mouse_old_y = 0; menu_ctx_pointer_t point; - point.x = menu_input_mouse_state(MENU_MOUSE_X_AXIS); - point.y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); + point.x = pointer_hw_state->x; + point.y = pointer_hw_state->y; point.ptr = 0; point.cbs = NULL; point.entry = NULL; point.action = 0; point.retcode = 0; - if (menu_input_dialog_get_display_kb_internal()) - menu_driver_ctl(RARCH_MENU_CTL_OSK_PTR_AT_POS, &point); - - if (rarch_timer_is_running(&mouse_activity_timer)) - rarch_timer_tick(&mouse_activity_timer); - - if (mouse_old_x != point.x || mouse_old_y != point.y) - { - if (!rarch_timer_is_running(&mouse_activity_timer)) - mouse_activity = true; + menu_driver_ctl(RARCH_MENU_CTL_OSK_PTR_AT_POS, &point); + if (point.retcode > -1) menu_event_set_osk_ptr(point.retcode); - } - else - { - if (rarch_timer_has_expired(&mouse_activity_timer)) - no_mouse_activity = true; - } - mouse_old_x = point.x; - mouse_old_y = point.y; } - if (BIT64_GET(mouse_state, MENU_MOUSE_ACTION_BUTTON_L)) + /* Select + X/Y position */ + if (!menu_input->select_inhibit) { - menu_ctx_pointer_t point; - - point.x = menu_input_mouse_state(MENU_MOUSE_X_AXIS); - point.y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); - point.ptr = menu_input->mouse.ptr; - point.cbs = cbs; - point.entry = entry; - point.action = action; - - if (menu_input_dialog_get_display_kb_internal()) + if (pointer_hw_state->select_pressed) { - menu_driver_ctl(RARCH_MENU_CTL_OSK_PTR_AT_POS, &point); - if (point.retcode > -1) + int16_t x = pointer_hw_state->x; + int16_t y = pointer_hw_state->y; + static float accel0 = 0.0f; + static float accel1 = 0.0f; + + /* Transition from select unpressed to select pressed */ + if (!last_select_pressed) { - menu_event_set_osk_ptr(point.retcode); - menu_event_osk_append(point.retcode); + menu_ctx_pointer_t point; + + /* Initialise variables */ + start_time = current_time; + start_x = x; + start_y = y; + last_x = x; + last_y = y; + accel0 = 0.0f; + accel1 = 0.0f; + + /* If we are not currently showing the onscreen keyboard, + * trigger a 'pointer down' event */ + if (!osk_active) + { + point.x = x; + point.y = y; + point.ptr = menu_input->ptr; + point.cbs = cbs; + point.entry = entry; + point.action = action; + + menu_driver_ctl(RARCH_MENU_CTL_POINTER_DOWN, &point); + ret = point.retcode; + } + } + else if (!osk_active) + { + /* Pointer is being held down (i.e. for more than one frame) + * Note: We do not track movement while the onscreen + * keyboard is displayed */ + gfx_ctx_metrics_t metrics; + float dpi; + + metrics.type = DISPLAY_METRIC_DPI; + metrics.value = &dpi; + + /* > Update deltas + acceleration + * Note: We only do this if the pointer has moved above + * a certain threshold - this requires dpi info */ + if (video_context_driver_get_metrics(&metrics)) + { + if ((abs(x - start_x) > (dpi / 10)) || + (abs(y - start_y) > (dpi / 10))) + { + menu_input->pointer.dragged = true; + + menu_input->pointer.dx = x - last_x; + menu_input->pointer.dy = y - last_y; + + /* Magic numbers... */ + menu_input->pointer.y_accel = (accel0 + accel1 + (float)menu_input->pointer.dy) / 3.0f; + accel0 = accel1; + accel1 = menu_input->pointer.y_accel; + + /* Acceleration decays over time - but if the value + * has been set on this frame, attenuation should + * be skipped */ + attenuate_y_accel = false; + } + else + { + /* Pointer is stationary */ + menu_input->pointer.dx = 0; + menu_input->pointer.dy = 0; + } + } + else + { + /* No dpi info - just fallback to zero... */ + menu_input->pointer.dx = 0; + menu_input->pointer.dy = 0; + menu_input->pointer.y_accel = 0.0f; + } + + /* > Update remaining variables */ + menu_input->pointer.press_duration = current_time - start_time; + last_x = x; + last_y = y; } } - else + else if (last_select_pressed) { - menu_driver_ctl(RARCH_MENU_CTL_POINTER_UP, &point); - menu_driver_ctl(RARCH_MENU_CTL_POINTER_TAP, &point); - ret = point.retcode; - } - } + /* Transition from select pressed to select unpressed */ - if (BIT64_GET(mouse_state, MENU_MOUSE_ACTION_BUTTON_R)) - { - size_t selection = menu_navigation_get_selection(); - menu_entry_action(entry, (unsigned)selection, MENU_ACTION_CANCEL); - } - - if (BIT64_GET(mouse_state, MENU_MOUSE_ACTION_WHEEL_DOWN)) - { - unsigned increment_by = 1; - menu_driver_ctl(MENU_NAVIGATION_CTL_INCREMENT, &increment_by); - } - - if (BIT64_GET(mouse_state, MENU_MOUSE_ACTION_WHEEL_UP)) - { - unsigned decrement_by = 1; - menu_driver_ctl(MENU_NAVIGATION_CTL_DECREMENT, &decrement_by); - } - - if (BIT64_GET(mouse_state, MENU_MOUSE_ACTION_HORIZ_WHEEL_UP)) - { - /* stub */ - } - - if (BIT64_GET(mouse_state, MENU_MOUSE_ACTION_HORIZ_WHEEL_DOWN)) - { - /* stub */ - } - - if (mouse_activity) - { - menu_ctx_environment_t menu_environ; - - rarch_timer_begin(&mouse_activity_timer, 4); - menu_environ.type = MENU_ENVIRON_ENABLE_MOUSE_CURSOR; - menu_environ.data = NULL; - - menu_driver_ctl(RARCH_MENU_CTL_ENVIRONMENT, &menu_environ); - } - - if (no_mouse_activity) - { - menu_ctx_environment_t menu_environ; - - rarch_timer_end(&mouse_activity_timer); - menu_environ.type = MENU_ENVIRON_DISABLE_MOUSE_CURSOR; - menu_environ.data = NULL; - - menu_driver_ctl(RARCH_MENU_CTL_ENVIRONMENT, &menu_environ); - } - - return ret; -} - -int16_t menu_input_pointer_state(enum menu_input_pointer_state state) -{ - menu_input_t *menu_input = &menu_input_state; - - if (!menu_input) - return 0; - - switch (state) - { - case MENU_POINTER_X_AXIS: - return menu_input->pointer.x; - case MENU_POINTER_Y_AXIS: - return menu_input->pointer.y; - case MENU_POINTER_DELTA_X_AXIS: - return menu_input->pointer.dx; - case MENU_POINTER_DELTA_Y_AXIS: - return menu_input->pointer.dy; - case MENU_POINTER_PRESSED: - return menu_input->pointer.pressed[0]; - } - - return 0; -} - -int16_t menu_input_mouse_state(enum menu_input_mouse_state state) -{ - rarch_joypad_info_t joypad_info; - unsigned type = 0; - unsigned device = RETRO_DEVICE_MOUSE; - - joypad_info.joy_idx = 0; - joypad_info.auto_binds = NULL; - joypad_info.axis_threshold = 0.0f; - - switch (state) - { - case MENU_MOUSE_X_AXIS: - device = RARCH_DEVICE_MOUSE_SCREEN; - type = RETRO_DEVICE_ID_MOUSE_X; - break; - case MENU_MOUSE_Y_AXIS: - device = RARCH_DEVICE_MOUSE_SCREEN; - type = RETRO_DEVICE_ID_MOUSE_Y; - break; - case MENU_MOUSE_LEFT_BUTTON: - type = RETRO_DEVICE_ID_MOUSE_LEFT; - break; - case MENU_MOUSE_RIGHT_BUTTON: - type = RETRO_DEVICE_ID_MOUSE_RIGHT; - break; - case MENU_MOUSE_WHEEL_UP: - type = RETRO_DEVICE_ID_MOUSE_WHEELUP; - break; - case MENU_MOUSE_WHEEL_DOWN: - type = RETRO_DEVICE_ID_MOUSE_WHEELDOWN; - break; - case MENU_MOUSE_HORIZ_WHEEL_UP: - type = RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELUP; - break; - case MENU_MOUSE_HORIZ_WHEEL_DOWN: - type = RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN; - break; - } - - return current_input->input_state(current_input_data, joypad_info, - NULL, 0, device, 0, type); -} - -static int menu_input_pointer_post_iterate( - menu_file_list_cbs_t *cbs, - menu_entry_t *entry, unsigned action) -{ - static bool pointer_oldpressed[2]; - static bool pointer_oldback = false; - static int16_t start_x = 0; - static int16_t start_y = 0; - static int16_t pointer_old_x = 0; - static int16_t pointer_old_y = 0; - int ret = 0; - menu_input_t *menu_input = &menu_input_state; - settings_t *settings = configuration_settings; - -#ifdef HAVE_OVERLAY - /* If we have overlays enabled, overlay controls take - * precedence and we don't want regular menu - * pointer controls to be handled */ - if (( settings->bools.input_overlay_enable - && overlay_ptr && overlay_ptr->alive)) - return 0; -#endif - - if (menu_input->pointer.pressed[0]) - { - gfx_ctx_metrics_t metrics; - float dpi; - static float accel0 = 0.0f; - static float accel1 = 0.0f; - int16_t pointer_x = menu_input_pointer_state(MENU_POINTER_X_AXIS); - int16_t pointer_y = menu_input_pointer_state(MENU_POINTER_Y_AXIS); - - metrics.type = DISPLAY_METRIC_DPI; - metrics.value = &dpi; - - menu_input->pointer.counter++; - - if (menu_input->pointer.counter == 1 && !pointer_dragging) - { - menu_ctx_pointer_t point; - - point.x = pointer_x; - point.y = pointer_y; - point.ptr = menu_input->pointer.ptr; - point.cbs = cbs; - point.entry = entry; - point.action = action; - - menu_driver_ctl(RARCH_MENU_CTL_POINTER_DOWN, &point); - } - - if (!pointer_oldpressed[0]) - { - menu_input->pointer.accel = 0; - accel0 = 0; - accel1 = 0; - start_x = pointer_x; - start_y = pointer_y; - pointer_old_x = pointer_x; - pointer_old_y = pointer_y; - pointer_oldpressed[0] = true; - } - else if (video_context_driver_get_metrics(&metrics)) - { - if (abs(pointer_x - start_x) > (dpi / 10) - || abs(pointer_y - start_y) > (dpi / 10)) - { - float s; - - pointer_dragging = true; - menu_input->pointer.dx = pointer_x - pointer_old_x; - menu_input->pointer.dy = pointer_y - pointer_old_y; - pointer_old_x = pointer_x; - pointer_old_y = pointer_y; - - s = menu_input->pointer.dy; - menu_input->pointer.accel = (accel0 + accel1 + s) / 3; - accel0 = accel1; - accel1 = menu_input->pointer.accel; - } - } - } - else - { - if (pointer_oldpressed[0]) - { - if (!pointer_dragging) + /* If pointer has been 'dragged', then it counts as + * a miss. Only register 'release' event if pointer + * has remained stationary + * TODO/FIXME: Releasing select should *always* trigger a + * pointer up event, but event type should be specified + * - i.e. long press, short press, swipe left/right. + * The menu driver can then choose how to interpret the + * user action */ + if (!menu_input->pointer.dragged) { menu_ctx_pointer_t point; point.x = start_x; point.y = start_y; - point.ptr = menu_input->pointer.ptr; + point.ptr = menu_input->ptr; point.cbs = cbs; point.entry = entry; point.action = action; - if (menu_input_dialog_get_display_kb_internal()) + /* On screen keyboard overrides normal menu input */ + if (osk_active) { menu_driver_ctl(RARCH_MENU_CTL_OSK_PTR_AT_POS, &point); if (point.retcode > -1) @@ -13003,76 +13080,138 @@ static int menu_input_pointer_post_iterate( menu_event_set_osk_ptr(point.retcode); menu_event_osk_append(point.retcode); } + ret = point.retcode; } else { - if (menu_input->pointer.counter > 32) + /* A 'long press' triggers a start (reset to default) action */ + if (menu_input->pointer.press_duration > MENU_INPUT_PRESS_TIME_LONG) { size_t selection = menu_navigation_get_selection(); - if (cbs && cbs->action_start) - return menu_entry_action(entry, (unsigned)selection, MENU_ACTION_START); - + ret = menu_entry_action(entry, (unsigned)selection, MENU_ACTION_START); } else { menu_driver_ctl(RARCH_MENU_CTL_POINTER_UP, &point); - menu_driver_ctl(RARCH_MENU_CTL_POINTER_TAP, &point); ret = point.retcode; } } } - pointer_oldpressed[0] = false; - start_x = 0; - start_y = 0; - pointer_old_x = 0; - pointer_old_y = 0; - menu_input->pointer.dx = 0; - menu_input->pointer.dy = 0; - menu_input->pointer.counter = 0; - - pointer_dragging = false; + /* Reset variables */ + start_x = 0; + start_y = 0; + last_x = 0; + last_y = 0; + menu_input->pointer.press_duration = 0; + menu_input->pointer.dx = 0; + menu_input->pointer.dy = 0; + menu_input->pointer.dragged = false; } } + last_select_pressed = pointer_hw_state->select_pressed; - if (menu_input->pointer.back) + /* Adjust acceleration + * > If onscreen keyboard is shown, acceleration must + * be reset to zero + * > Otherwise, if acceleration has not been set on + * this frame, apply normal attenuation */ + if (osk_active) + menu_input->pointer.y_accel = 0.0f; + else if (attenuate_y_accel) + menu_input->pointer.y_accel *= MENU_INPUT_Y_ACCEL_DECAY_FACTOR; + + /* If select has been released, disable any existing + * select inhibit */ + if (!pointer_hw_state->select_pressed) + menu_input->select_inhibit = false; + + /* Cancel */ + if (pointer_hw_state->cancel_pressed && !last_cancel_pressed) { - if (!pointer_oldback) - { - pointer_oldback = true; - menu_entry_action(entry, (unsigned)menu_navigation_get_selection(), MENU_ACTION_CANCEL); - } + size_t selection = menu_navigation_get_selection(); + ret = menu_entry_action(entry, (unsigned)selection, MENU_ACTION_CANCEL); + } + last_cancel_pressed = pointer_hw_state->cancel_pressed; + + /* Up + * Note: This always corresponds to a mouse wheel, which + * handles differently from other inputs - i.e. we don't + * want a 'last pressed' check */ + if (pointer_hw_state->up_pressed) + { + size_t selection = menu_navigation_get_selection(); + ret = menu_entry_action(entry, (unsigned)selection, MENU_ACTION_UP); } - pointer_oldback = menu_input->pointer.back; + /* Down + * Note: This always corresponds to a mouse wheel, which + * handles differently from other inputs - i.e. we don't + * want a 'last pressed' check */ + if (pointer_hw_state->down_pressed) + { + size_t selection = menu_navigation_get_selection(); + ret = menu_entry_action(entry, (unsigned)selection, MENU_ACTION_DOWN); + } + + /* Left */ + if (pointer_hw_state->left_pressed && !last_left_pressed) + { + size_t selection = menu_navigation_get_selection(); + ret = menu_entry_action(entry, (unsigned)selection, MENU_ACTION_LEFT); + } + last_left_pressed = pointer_hw_state->left_pressed; + + /* Right */ + if (pointer_hw_state->right_pressed && !last_right_pressed) + { + size_t selection = menu_navigation_get_selection(); + ret = menu_entry_action(entry, (unsigned)selection, MENU_ACTION_RIGHT); + } + + menu_input_set_pointer_visibility(current_time); return ret; } void menu_input_post_iterate(int *ret, unsigned action) { - menu_entry_t entry; - settings_t *settings = configuration_settings; - file_list_t *selection_buf = menu_entries_get_selection_buf_ptr(0); - size_t selection = menu_navigation_get_selection(); - menu_file_list_cbs_t *cbs = selection_buf ? - (menu_file_list_cbs_t*)selection_buf->list[selection].actiondata - : NULL; + menu_input_t *menu_input = &menu_input_state; + retro_time_t current_time = cpu_features_get_time_usec(); - menu_entry_init(&entry); - /* Note: If menu_input_mouse_frame() or - * menu_input_pointer_post_iterate() are - * modified, will have to verify that these - * parameters remain unused... */ - entry.rich_label_enabled = false; - entry.value_enabled = false; - entry.sublabel_enabled = false; - menu_entry_get(&entry, 0, selection, NULL, false); + /* If pointer devices are disabled, just ensure mouse + * cursor is hidden */ + if (menu_input->pointer.type == MENU_POINTER_DISABLED) + { + /* Note: We have to call menu_input_set_pointer_visibility() + * here, otherwise the cursor state gets muddled up when + * toggling mouse/touchscreen support... + * It's a very light function, however, so there should + * be no performance impact */ + menu_input_set_pointer_visibility(current_time); + *ret = 0; + } + else + { + menu_entry_t entry; + settings_t *settings = configuration_settings; + file_list_t *selection_buf = menu_entries_get_selection_buf_ptr(0); + size_t selection = menu_navigation_get_selection(); + menu_file_list_cbs_t *cbs = selection_buf ? + (menu_file_list_cbs_t*)selection_buf->list[selection].actiondata + : NULL; - *ret = menu_input_mouse_frame(cbs, &entry, action); + menu_entry_init(&entry); + /* Note: If menu_input_pointer_post_iterate() is + * modified, will have to verify that these + * parameters remain unused... */ + entry.rich_label_enabled = false; + entry.value_enabled = false; + entry.sublabel_enabled = false; + menu_entry_get(&entry, 0, selection, NULL, false); - if (settings->bools.menu_pointer_enable) - *ret |= menu_input_pointer_post_iterate(cbs, &entry, action); + *ret = menu_input_pointer_post_iterate(current_time, cbs, &entry, action); + } } /** @@ -22837,6 +22976,11 @@ static void menu_driver_toggle(bool on) menu_driver_alive = on; + /* Apply any required menu pointer input inhibits + * (i.e. prevent phantom input when using an overlay + * to toggle the menu on) */ + menu_input_driver_toggle(on); + if (menu_driver_alive) { bool refresh = false;