iOS: Long-touch context menu. Work around event queue delays.
This commit is contained in:
parent
8668b7fcb7
commit
941844f14a
6 changed files with 142 additions and 46 deletions
|
@ -19,7 +19,6 @@
|
|||
#include "events.hpp"
|
||||
#include "game_config_manager.hpp"
|
||||
#include "hotkey/command_executor.hpp"
|
||||
#include "hotkey/hotkey_command.hpp"
|
||||
#include "log.hpp"
|
||||
#include "map/map.hpp"
|
||||
#include "mouse_handler_base.hpp"
|
||||
|
@ -28,9 +27,13 @@
|
|||
#include "show_dialog.hpp" //gui::in_dialog
|
||||
#include "gui/core/event/handler.hpp" // gui2::is_in_dialog
|
||||
#include "soundsource.hpp"
|
||||
#include "gui/core/timer.hpp"
|
||||
|
||||
static lg::log_domain log_display("display");
|
||||
#define ERR_DP LOG_STREAM(err, log_display)
|
||||
|
||||
static const int long_touch_duration_ms = 800;
|
||||
|
||||
controller_base::controller_base()
|
||||
: game_config_(game_config_manager::get()->game_config())
|
||||
, key_()
|
||||
|
@ -41,11 +44,48 @@ controller_base::controller_base()
|
|||
, scroll_right_(false)
|
||||
, joystick_manager_()
|
||||
, key_release_listener_(*this)
|
||||
, last_mouse_is_touch_(false)
|
||||
, long_touch_timer_(0)
|
||||
{
|
||||
}
|
||||
|
||||
controller_base::~controller_base()
|
||||
{
|
||||
if(long_touch_timer_ != 0) {
|
||||
gui2::remove_timer(long_touch_timer_);
|
||||
long_touch_timer_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void controller_base::long_touch_callback(int x, int y)
|
||||
{
|
||||
if(long_touch_timer_ != 0 && !get_mouse_handler_base().dragging_started()) {
|
||||
int x_now;
|
||||
int y_now;
|
||||
uint32_t mouse_state = SDL_GetMouseState(&x_now, &y_now);
|
||||
|
||||
#ifdef MOUSE_TOUCH_EMULATION
|
||||
if(mouse_state & SDL_BUTTON(SDL_BUTTON_RIGHT)) {
|
||||
// Monkey-patch touch controls again to make them look like left button.
|
||||
mouse_state = SDL_BUTTON(SDL_BUTTON_LEFT);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Workaround for double-menu b/c of slow events processing, or I don't know.
|
||||
int dx = x - x_now;
|
||||
int dy = y - y_now;
|
||||
int threshold = get_mouse_handler_base().drag_threshold();
|
||||
bool yes_actually_dragging = dx * dx + dy * dy >= threshold * threshold;
|
||||
|
||||
if(!yes_actually_dragging && (mouse_state & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0) {
|
||||
const theme::menu* const m = get_mouse_handler_base().gui().get_theme().context_menu();
|
||||
if(m != nullptr) {
|
||||
show_menu(get_display().get_theme().context_menu()->items(), x_now, y_now, true, get_display());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long_touch_timer_ = 0;
|
||||
}
|
||||
|
||||
void controller_base::handle_event(const SDL_Event& event)
|
||||
|
@ -131,6 +171,14 @@ void controller_base::handle_event(const SDL_Event& event)
|
|||
break;
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
last_mouse_is_touch_ = event.button.which == SDL_TOUCH_MOUSEID;
|
||||
|
||||
if(last_mouse_is_touch_ && long_touch_timer_ == 0) {
|
||||
long_touch_timer_ = gui2::add_timer(
|
||||
long_touch_duration_ms,
|
||||
std::bind(&controller_base::long_touch_callback, this, event.button.x, event.button.y));
|
||||
}
|
||||
|
||||
mh_base.mouse_press(event.button, is_browsing());
|
||||
hotkey::mbutton_event(event, get_hotkey_command_executor());
|
||||
break;
|
||||
|
@ -140,6 +188,13 @@ void controller_base::handle_event(const SDL_Event& event)
|
|||
break;
|
||||
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
if(long_touch_timer_ != 0) {
|
||||
gui2::remove_timer(long_touch_timer_);
|
||||
long_touch_timer_ = 0;
|
||||
}
|
||||
|
||||
last_mouse_is_touch_ = event.button.which == SDL_TOUCH_MOUSEID;
|
||||
|
||||
mh_base.mouse_press(event.button, is_browsing());
|
||||
if(mh_base.get_show_menu()) {
|
||||
show_menu(get_display().get_theme().context_menu()->items(), event.button.x, event.button.y, true,
|
||||
|
@ -159,6 +214,10 @@ void controller_base::handle_event(const SDL_Event& event)
|
|||
#endif
|
||||
break;
|
||||
|
||||
case TIMER_EVENT:
|
||||
gui2::execute_timer(reinterpret_cast<size_t>(event.user.data1));
|
||||
break;
|
||||
|
||||
// TODO: Support finger specifically, like pan the map. For now, SDL's "shadow mouse" events will do.
|
||||
case SDL_MULTIGESTURE:
|
||||
default:
|
||||
|
|
|
@ -178,6 +178,8 @@ protected:
|
|||
virtual void execute_action(const std::vector<std::string>& items_arg, int xloc, int yloc, bool context_menu);
|
||||
|
||||
virtual bool in_context_menu(hotkey::HOTKEY_COMMAND command) const;
|
||||
|
||||
void long_touch_callback(int x, int y);
|
||||
|
||||
const config& game_config_;
|
||||
|
||||
|
@ -213,4 +215,8 @@ private:
|
|||
};
|
||||
|
||||
keyup_listener key_release_listener_;
|
||||
|
||||
bool last_mouse_is_touch_;
|
||||
/** Context menu timer */
|
||||
size_t long_touch_timer_;
|
||||
};
|
||||
|
|
|
@ -37,54 +37,12 @@ REGISTER_DIALOG(drop_down_menu)
|
|||
|
||||
namespace
|
||||
{
|
||||
void click_callback(window& window, bool keep_open, const point& coordinate)
|
||||
{
|
||||
listbox& list = find_widget<listbox>(&window, "list", true);
|
||||
|
||||
/* Disregard clicks on scrollbars and toggle buttons so the dropdown menu can be scrolled or have an embedded
|
||||
* toggle button selected without the menu closing.
|
||||
*
|
||||
* This works since this click_callback function is called before widgets' left-button-up handlers.
|
||||
*
|
||||
* Additionally, this is done before row deselection so selecting/deselecting a toggle button doesn't also leave
|
||||
* the list with no row visually selected. Oddly, the visual deselection doesn't seem to cause any crashes, and
|
||||
* the previously selected row is reselected when the menu is opened again. Still, it's odd to see your selection
|
||||
* vanish.
|
||||
*/
|
||||
if(list.vertical_scrollbar()->get_state() == scrollbar_base::PRESSED) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(dynamic_cast<toggle_button*>(window.find_at(coordinate, true)) != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* FIXME: This dialog uses a listbox with 'has_minimum = false'. This allows a listbox to have 0 or 1 selections,
|
||||
* and selecting the same entry toggles that entry's state (ie, if it was selected, it will be deselected). Because
|
||||
* of this, selecting the same entry in the dropdown list essentially sets the list's selected row to -1, causing problems.
|
||||
*
|
||||
* In order to work around this, we first manually deselect the selected entry here. This handler is called *before*
|
||||
* the listbox's click handler, and as such the selected item will remain toggled on when the click handler fires.
|
||||
*/
|
||||
const int sel = list.get_selected_row();
|
||||
if(sel >= 0) {
|
||||
list.select_row(sel, false);
|
||||
}
|
||||
|
||||
SDL_Rect rect = window.get_rectangle();
|
||||
if(!sdl::point_in_rect(coordinate, rect)) {
|
||||
window.set_retval(retval::CANCEL);
|
||||
} else if(!keep_open) {
|
||||
window.set_retval(retval::OK);
|
||||
}
|
||||
}
|
||||
|
||||
void callback_flip_embedded_toggle(window& window)
|
||||
{
|
||||
listbox& list = find_widget<listbox>(&window, "list", true);
|
||||
|
||||
/* If the currently selected row has a toggle button, toggle it.
|
||||
* Note this cannot be handled in click_callback since at that point the new row selection has not registered,
|
||||
* Note this cannot be handled in mouse_up_callback since at that point the new row selection has not registered,
|
||||
* meaning the currently selected row's button is toggled.
|
||||
*/
|
||||
grid* row_grid = list.get_row_grid(list.get_selected_row());
|
||||
|
@ -99,6 +57,57 @@ namespace
|
|||
}
|
||||
}
|
||||
|
||||
void drop_down_menu::mouse_up_callback(window& window, bool&, bool&, const point& coordinate)
|
||||
{
|
||||
if(!mouse_down_happened_) {
|
||||
return;
|
||||
}
|
||||
|
||||
listbox& list = find_widget<listbox>(&window, "list", true);
|
||||
|
||||
/* Disregard clicks on scrollbars and toggle buttons so the dropdown menu can be scrolled or have an embedded
|
||||
* toggle button selected without the menu closing.
|
||||
*
|
||||
* This works since this mouse_up_callback function is called before widgets' left-button-up handlers.
|
||||
*
|
||||
* Additionally, this is done before row deselection so selecting/deselecting a toggle button doesn't also leave
|
||||
* the list with no row visually selected. Oddly, the visial deselection doesn't seem to cause any crashes, and
|
||||
* the previously selected row is reselected when the menu is opened again. Still, it's odd to see your selection
|
||||
* vanish.
|
||||
*/
|
||||
if(list.vertical_scrollbar()->get_state() == scrollbar_base::PRESSED) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(dynamic_cast<toggle_button*>(window.find_at(coordinate, true)) != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* FIXME: This dialog uses a listbox with 'has_minimum = false'. This allows a listbox to have 0 or 1 selections,
|
||||
* and selecting the same entry toggles that entry's state (ie, if it was selected, it will be deselected). Because
|
||||
* of this, selecting the same entry in the dropdown list essentially sets the list's selected row to -1, causing problems.
|
||||
*
|
||||
* In order to work around this, we first manually deselect the selected entry here. This handler is called *before*
|
||||
* the listbox's click handler, and as such the selected item will remain toggled on when the click handler fires.
|
||||
*/
|
||||
const int sel = list.get_selected_row();
|
||||
if(sel >= 0) {
|
||||
list.select_row(sel, false);
|
||||
}
|
||||
|
||||
SDL_Rect rect = window.get_rectangle();
|
||||
if(!sdl::point_in_rect(coordinate, rect)) {
|
||||
window.set_retval(retval::CANCEL);
|
||||
} else if(!keep_open_) {
|
||||
window.set_retval(retval::OK);
|
||||
}
|
||||
}
|
||||
|
||||
void drop_down_menu::mouse_down_callback()
|
||||
{
|
||||
mouse_down_happened_ = true;
|
||||
}
|
||||
|
||||
void drop_down_menu::pre_show(window& window)
|
||||
{
|
||||
window.set_variable("button_x", wfl::variant(button_pos_.x));
|
||||
|
@ -181,10 +190,13 @@ void drop_down_menu::pre_show(window& window)
|
|||
|
||||
// Dismiss on clicking outside the window.
|
||||
window.connect_signal<event::SDL_LEFT_BUTTON_UP>(
|
||||
std::bind(&click_callback, std::ref(window), keep_open_, _5), event::dispatcher::front_child);
|
||||
std::bind(&drop_down_menu::mouse_up_callback, this, std::ref(window), _3, _4, _5), event::dispatcher::front_child);
|
||||
|
||||
window.connect_signal<event::SDL_RIGHT_BUTTON_UP>(
|
||||
std::bind(&click_callback, std::ref(window), keep_open_, _5), event::dispatcher::front_child);
|
||||
std::bind(&drop_down_menu::mouse_up_callback, this, std::ref(window), _3, _4, _5), event::dispatcher::front_child);
|
||||
|
||||
window.connect_signal<event::SDL_LEFT_BUTTON_DOWN>(
|
||||
std::bind(&drop_down_menu::mouse_down_callback, this), event::dispatcher::front_child);
|
||||
|
||||
// Dismiss on resize.
|
||||
window.connect_signal<event::SDL_VIDEO_RESIZE>(
|
||||
|
|
|
@ -37,6 +37,7 @@ public:
|
|||
, selected_item_(selected_item)
|
||||
, use_markup_(use_markup)
|
||||
, keep_open_(keep_open)
|
||||
, mouse_down_happened_(false)
|
||||
, callback_toggle_state_change_(callback_toggle_state_change)
|
||||
{
|
||||
set_restore(true);
|
||||
|
@ -71,6 +72,12 @@ private:
|
|||
*/
|
||||
bool keep_open_;
|
||||
|
||||
/**
|
||||
* When menu is invoked on a long-touch timer, a following mouse-up event will close it.
|
||||
* This flag prevents that: the menu will only be closed on a mouse-up that follows a mouse-down.
|
||||
* */
|
||||
bool mouse_down_happened_;
|
||||
|
||||
/**
|
||||
* If toggle buttons are used, this callback is called whenever the state of any toggle
|
||||
* button changes.
|
||||
|
@ -85,6 +92,10 @@ private:
|
|||
|
||||
/** Inherited from modal_dialog. */
|
||||
virtual void post_show(window& window) override;
|
||||
|
||||
void mouse_up_callback(window& window, bool&, bool&, const point& coordinate);
|
||||
|
||||
void mouse_down_callback();
|
||||
};
|
||||
|
||||
} // namespace dialogs
|
||||
|
|
|
@ -66,6 +66,11 @@ mouse_handler_base::mouse_handler_base()
|
|||
{
|
||||
}
|
||||
|
||||
bool mouse_handler_base::dragging_started() const
|
||||
{
|
||||
return dragging_started_;
|
||||
}
|
||||
|
||||
bool mouse_handler_base::is_dragging() const
|
||||
{
|
||||
return dragging_left_ || dragging_right_ || dragging_touch_;
|
||||
|
|
|
@ -48,6 +48,9 @@ public:
|
|||
|
||||
/** Const version of @ref gui */
|
||||
virtual const display& gui() const = 0;
|
||||
|
||||
/** If mouse/finger has moved far enough to consider it move/swipe, and not a click/touch */
|
||||
bool dragging_started() const;
|
||||
|
||||
/**
|
||||
* @return true when the class in the "dragging" state.
|
||||
|
|
Loading…
Add table
Reference in a new issue