Skip to content

Commit

Permalink
Add scroll wheel support to desktop GLFW shell (#8416)
Browse files Browse the repository at this point in the history
Sends scroll events from GLFW to the Flutter engine, allowing scrolling
of Scrollables via a scroll wheel.
  • Loading branch information
stuartmorgan committed Apr 2, 2019
1 parent 77d8e12 commit a650790
Showing 1 changed file with 76 additions and 30 deletions.
106 changes: 76 additions & 30 deletions shell/platform/glfw/flutter_glfw.cc
Expand Up @@ -155,57 +155,86 @@ static void GLFWFramebufferSizeCallback(GLFWwindow* window,
FlutterEngineSendWindowMetricsEvent(state->engine, &event);
}

// Sends a pointer event to the Flutter engine with the given phase.
static void SendPointerEventWithPhase(GLFWwindow* window,
FlutterPointerPhase phase,
double x,
double y) {
auto state = GetSavedWindowState(window);
// Sends a pointer event to the Flutter engine based on the given data.
//
// Any coordinate/distance values in |event_data| should be in screen
// coordinates; they will be adjusted to pixel values before being sent.
static void SendPointerEventWithData(GLFWwindow* window,
const FlutterPointerEvent& event_data) {
auto* state = GetSavedWindowState(window);
// If sending anything other than an add, and the pointer isn't already added,
// synthesize an add to satisfy Flutter's expectations about events.
if (!state->pointer_currently_added && phase != FlutterPointerPhase::kAdd) {
SendPointerEventWithPhase(window, FlutterPointerPhase::kAdd, x, y);
if (!state->pointer_currently_added &&
event_data.phase != FlutterPointerPhase::kAdd) {
FlutterPointerEvent event = {};
event.phase = FlutterPointerPhase::kAdd;
event.x = event_data.x;
event.y = event_data.y;
SendPointerEventWithData(window, event);
}
// Don't double-add (e.g., if events are delivered out of order, so an add has
// already been synthesized).
if (state->pointer_currently_added && phase == FlutterPointerPhase::kAdd) {
if (state->pointer_currently_added &&
event_data.phase == FlutterPointerPhase::kAdd) {
return;
}

FlutterPointerEvent event = {};
FlutterPointerEvent event = event_data;
// Set metadata that's always the same regardless of the event.
event.struct_size = sizeof(event);
event.phase = phase;
event.x = x * state->window_pixels_per_screen_coordinate;
event.y = y * state->window_pixels_per_screen_coordinate;
event.timestamp =
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch())
.count();
// Convert all screen coordinates to pixel coordinates.
event.x *= state->window_pixels_per_screen_coordinate;
event.y *= state->window_pixels_per_screen_coordinate;
event.scroll_delta_x *= state->window_pixels_per_screen_coordinate;
event.scroll_delta_y *= state->window_pixels_per_screen_coordinate;

FlutterEngineSendPointerEvent(state->engine, &event, 1);

if (phase == FlutterPointerPhase::kAdd) {
if (event_data.phase == FlutterPointerPhase::kAdd) {
state->pointer_currently_added = true;
} else if (phase == FlutterPointerPhase::kRemove) {
} else if (event_data.phase == FlutterPointerPhase::kRemove) {
state->pointer_currently_added = false;
}
}

// Updates |event_data| with the current location of the mouse cursor.
static void SetEventLocationFromCursorPosition(
GLFWwindow* window,
FlutterPointerEvent* event_data) {
glfwGetCursorPos(window, &event_data->x, &event_data->y);
}

// Set's |event_data|'s phase to either kMove or kHover depending on the current
// primary mouse button state.
static void SetEventPhaseFromCursorButtonState(
GLFWwindow* window,
FlutterPointerEvent* event_data) {
event_data->phase =
glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS
? FlutterPointerPhase::kMove
: FlutterPointerPhase::kHover;
}

// Reports the mouse entering or leaving the Flutter view.
static void GLFWCursorEnterCallback(GLFWwindow* window, int entered) {
double x, y;
glfwGetCursorPos(window, &x, &y);
FlutterPointerPhase phase =
FlutterPointerEvent event = {};
event.phase =
entered ? FlutterPointerPhase::kAdd : FlutterPointerPhase::kRemove;
SendPointerEventWithPhase(window, phase, x, y);
SetEventLocationFromCursorPosition(window, &event);
SendPointerEventWithData(window, event);
}

// Reports mouse movement to the Flutter engine.
static void GLFWCursorPositionCallback(GLFWwindow* window, double x, double y) {
bool button_down =
glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS;
FlutterPointerPhase phase =
button_down ? FlutterPointerPhase::kMove : FlutterPointerPhase::kHover;
SendPointerEventWithPhase(window, phase, x, y);
FlutterPointerEvent event = {};
event.x = x;
event.y = y;
SetEventPhaseFromCursorButtonState(window, &event);
SendPointerEventWithData(window, event);
}

// Reports mouse button press to the Flutter engine.
Expand All @@ -219,12 +248,11 @@ static void GLFWMouseButtonCallback(GLFWwindow* window,
return;
}

double x, y;
glfwGetCursorPos(window, &x, &y);
FlutterPointerPhase phase = (action == GLFW_PRESS)
? FlutterPointerPhase::kDown
: FlutterPointerPhase::kUp;
SendPointerEventWithPhase(window, phase, x, y);
FlutterPointerEvent event = {};
event.phase = (action == GLFW_PRESS) ? FlutterPointerPhase::kDown
: FlutterPointerPhase::kUp;
SetEventLocationFromCursorPosition(window, &event);
SendPointerEventWithData(window, event);

// If mouse tracking isn't already enabled, turn it on for the duration of
// the drag to generate kMove events.
Expand All @@ -242,6 +270,22 @@ static void GLFWMouseButtonCallback(GLFWwindow* window,
}
}

// Reports scroll wheel events to the Flutter engine.
static void GLFWScrollCallback(GLFWwindow* window,
double delta_x,
double delta_y) {
FlutterPointerEvent event = {};
SetEventLocationFromCursorPosition(window, &event);
SetEventPhaseFromCursorButtonState(window, &event);
event.signal_kind = FlutterPointerSignalKind::kFlutterPointerSignalKindScroll;
// TODO: See if this can be queried from the OS; this value is chosen
// arbitrarily to get something that feels reasonable.
const int kScrollOffsetMultiplier = 20;
event.scroll_delta_x = delta_x * kScrollOffsetMultiplier;
event.scroll_delta_y = -delta_y * kScrollOffsetMultiplier;
SendPointerEventWithData(window, event);
}

// Passes character input events to registered handlers.
static void GLFWCharCallback(GLFWwindow* window, unsigned int code_point) {
for (const auto& handler :
Expand Down Expand Up @@ -276,6 +320,7 @@ static void GLFWAssignEventCallbacks(GLFWwindow* window) {
glfwSetKeyCallback(window, GLFWKeyCallback);
glfwSetCharCallback(window, GLFWCharCallback);
glfwSetMouseButtonCallback(window, GLFWMouseButtonCallback);
glfwSetScrollCallback(window, GLFWScrollCallback);
if (GetSavedWindowState(window)->hover_tracking_enabled) {
SetHoverCallbacksEnabled(window, true);
}
Expand All @@ -286,6 +331,7 @@ static void GLFWClearEventCallbacks(GLFWwindow* window) {
glfwSetKeyCallback(window, nullptr);
glfwSetCharCallback(window, nullptr);
glfwSetMouseButtonCallback(window, nullptr);
glfwSetScrollCallback(window, nullptr);
SetHoverCallbacksEnabled(window, false);
}

Expand Down

0 comments on commit a650790

Please sign in to comment.