Skip to content

Commit

Permalink
iOS: Long-touch context menu. Work around event queue delays.
Browse files Browse the repository at this point in the history
  • Loading branch information
singalen authored and jyrkive committed Oct 28, 2018
1 parent 8668b7f commit 941844f
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 46 deletions.
61 changes: 60 additions & 1 deletion src/controller_base.cpp
Expand Up @@ -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"
Expand All @@ -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_()
Expand All @@ -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)
Expand Down Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions src/controller_base.hpp
Expand Up @@ -178,6 +178,8 @@ class controller_base : public video2::draw_layering, public events::pump_monito
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_;

Expand Down Expand Up @@ -213,4 +215,8 @@ class controller_base : public video2::draw_layering, public events::pump_monito
};

keyup_listener key_release_listener_;

bool last_mouse_is_touch_;
/** Context menu timer */
size_t long_touch_timer_;
};
102 changes: 57 additions & 45 deletions src/gui/dialogs/drop_down_menu.cpp
Expand Up @@ -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());
Expand All @@ -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));
Expand Down Expand Up @@ -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>(
Expand Down
11 changes: 11 additions & 0 deletions src/gui/dialogs/drop_down_menu.hpp
Expand Up @@ -37,6 +37,7 @@ class drop_down_menu : public modal_dialog
, 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);
Expand Down Expand Up @@ -71,6 +72,12 @@ class drop_down_menu : public modal_dialog
*/
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.
Expand All @@ -85,6 +92,10 @@ class drop_down_menu : public modal_dialog

/** 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
Expand Down
5 changes: 5 additions & 0 deletions src/mouse_handler_base.cpp
Expand Up @@ -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_;
Expand Down
3 changes: 3 additions & 0 deletions src/mouse_handler_base.hpp
Expand Up @@ -48,6 +48,9 @@ class mouse_handler_base

/** 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.
Expand Down

0 comments on commit 941844f

Please sign in to comment.