Skip to content

Commit

Permalink
Linux trackpad gestures (flutter#31592)
Browse files Browse the repository at this point in the history
  • Loading branch information
moffatman authored May 23, 2022
1 parent f065dc1 commit 178adb3
Show file tree
Hide file tree
Showing 11 changed files with 1,152 additions and 28 deletions.
5 changes: 5 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -2210,6 +2210,11 @@ FILE: ../../../flutter/shell/platform/linux/fl_renderer_gl.cc
FILE: ../../../flutter/shell/platform/linux/fl_renderer_gl.h
FILE: ../../../flutter/shell/platform/linux/fl_renderer_headless.cc
FILE: ../../../flutter/shell/platform/linux/fl_renderer_headless.h
FILE: ../../../flutter/shell/platform/linux/fl_scrolling_manager.cc
FILE: ../../../flutter/shell/platform/linux/fl_scrolling_manager.h
FILE: ../../../flutter/shell/platform/linux/fl_scrolling_manager_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_scrolling_view_delegate.cc
FILE: ../../../flutter/shell/platform/linux/fl_scrolling_view_delegate.h
FILE: ../../../flutter/shell/platform/linux/fl_settings.cc
FILE: ../../../flutter/shell/platform/linux/fl_settings.h
FILE: ../../../flutter/shell/platform/linux/fl_settings_plugin.cc
Expand Down
3 changes: 3 additions & 0 deletions shell/platform/linux/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ source_set("flutter_linux_sources") {
"fl_renderer.cc",
"fl_renderer_gl.cc",
"fl_renderer_headless.cc",
"fl_scrolling_manager.cc",
"fl_scrolling_view_delegate.cc",
"fl_settings.cc",
"fl_settings_plugin.cc",
"fl_settings_portal.cc",
Expand Down Expand Up @@ -208,6 +210,7 @@ executable("flutter_linux_unittests") {
"fl_method_response_test.cc",
"fl_pixel_buffer_texture_test.cc",
"fl_plugin_registrar_test.cc",
"fl_scrolling_manager_test.cc",
"fl_settings_plugin_test.cc",
"fl_settings_portal_test.cc",
"fl_standard_message_codec_test.cc",
Expand Down
36 changes: 36 additions & 0 deletions shell/platform/linux/fl_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
// Unique number associated with platform tasks.
static constexpr size_t kPlatformTaskRunnerIdentifier = 1;

// Use different device ID for mouse and pan/zoom events, since we can't
// differentiate the actual device (mouse v.s. trackpad)
static constexpr int32_t kMousePointerDeviceId = 0;
static constexpr int32_t kPointerPanZoomDeviceId = 1;

struct _FlEngine {
GObject parent_instance;

Expand Down Expand Up @@ -729,6 +734,37 @@ void fl_engine_send_mouse_pointer_event(FlEngine* self,
fl_event.scroll_delta_y = scroll_delta_y;
fl_event.device_kind = kFlutterPointerDeviceKindMouse;
fl_event.buttons = buttons;
fl_event.device = kMousePointerDeviceId;
self->embedder_api.SendPointerEvent(self->engine, &fl_event, 1);
}

void fl_engine_send_pointer_pan_zoom_event(FlEngine* self,
size_t timestamp,
double x,
double y,
FlutterPointerPhase phase,
double pan_x,
double pan_y,
double scale,
double rotation) {
g_return_if_fail(FL_IS_ENGINE(self));

if (self->engine == nullptr) {
return;
}

FlutterPointerEvent fl_event = {};
fl_event.struct_size = sizeof(fl_event);
fl_event.timestamp = timestamp;
fl_event.x = x;
fl_event.y = y;
fl_event.phase = phase;
fl_event.pan_x = pan_x;
fl_event.pan_y = pan_y;
fl_event.scale = scale;
fl_event.rotation = rotation;
fl_event.device = kPointerPanZoomDeviceId;
fl_event.device_kind = kFlutterPointerDeviceKindTrackpad;
self->embedder_api.SendPointerEvent(self->engine, &fl_event, 1);
}

Expand Down
10 changes: 10 additions & 0 deletions shell/platform/linux/fl_engine_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,16 @@ void fl_engine_send_mouse_pointer_event(FlEngine* engine,
double scroll_delta_y,
int64_t buttons);

void fl_engine_send_pointer_pan_zoom_event(FlEngine* self,
size_t timestamp,
double x,
double y,
FlutterPointerPhase phase,
double pan_x,
double pan_y,
double scale,
double rotation);

/**
* fl_engine_send_key_event:
*/
Expand Down
37 changes: 37 additions & 0 deletions shell/platform/linux/fl_engine_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,43 @@ TEST(FlEngineTest, MousePointer) {
EXPECT_TRUE(called);
}

// Checks sending pan/zoom events works.
TEST(FlEngineTest, PointerPanZoom) {
g_autoptr(FlEngine) engine = make_mock_engine();
FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine);

bool called = false;
embedder_api->SendPointerEvent = MOCK_ENGINE_PROC(
SendPointerEvent,
([&called](auto engine, const FlutterPointerEvent* events,
size_t events_count) {
called = true;
EXPECT_EQ(events_count, static_cast<size_t>(1));
EXPECT_EQ(events[0].phase, kPanZoomUpdate);
EXPECT_EQ(events[0].timestamp, static_cast<size_t>(1234567890));
EXPECT_EQ(events[0].x, 800);
EXPECT_EQ(events[0].y, 600);
EXPECT_EQ(events[0].device, static_cast<int32_t>(1));
EXPECT_EQ(events[0].signal_kind, kFlutterPointerSignalKindNone);
EXPECT_EQ(events[0].pan_x, 1.5);
EXPECT_EQ(events[0].pan_y, 2.5);
EXPECT_EQ(events[0].scale, 3.5);
EXPECT_EQ(events[0].rotation, 4.5);
EXPECT_EQ(events[0].device_kind, kFlutterPointerDeviceKindTrackpad);
EXPECT_EQ(events[0].buttons, 0);

return kSuccess;
}));

g_autoptr(GError) error = nullptr;
EXPECT_TRUE(fl_engine_start(engine, &error));
EXPECT_EQ(error, nullptr);
fl_engine_send_pointer_pan_zoom_event(engine, 1234567890, 800, 600,
kPanZoomUpdate, 1.5, 2.5, 3.5, 4.5);

EXPECT_TRUE(called);
}

// Checks dispatching a semantics action works.
TEST(FlEngineTest, DispatchSemanticsAction) {
g_autoptr(FlEngine) engine = make_mock_engine();
Expand Down
181 changes: 181 additions & 0 deletions shell/platform/linux/fl_scrolling_manager.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/shell/platform/linux/fl_scrolling_manager.h"

static constexpr int kMicrosecondsPerMillisecond = 1000;

struct _FlScrollingManager {
GObject parent_instance;

FlScrollingViewDelegate* view_delegate;

gdouble last_x;
gdouble last_y;

gboolean pan_started;
gdouble pan_x;
gdouble pan_y;

gboolean zoom_started;
gboolean rotate_started;
gdouble scale;
gdouble rotation;
};

G_DEFINE_TYPE(FlScrollingManager, fl_scrolling_manager, G_TYPE_OBJECT);

static void fl_scrolling_manager_dispose(GObject* object);

static void fl_scrolling_manager_class_init(FlScrollingManagerClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_scrolling_manager_dispose;
}

static void fl_scrolling_manager_init(FlScrollingManager* self) {}

static void fl_scrolling_manager_dispose(GObject* object) {
G_OBJECT_CLASS(fl_scrolling_manager_parent_class)->dispose(object);
}

FlScrollingManager* fl_scrolling_manager_new(
FlScrollingViewDelegate* view_delegate) {
g_return_val_if_fail(FL_IS_SCROLLING_VIEW_DELEGATE(view_delegate), nullptr);

FlScrollingManager* self = FL_SCROLLING_MANAGER(
g_object_new(fl_scrolling_manager_get_type(), nullptr));

self->view_delegate = view_delegate;
g_object_add_weak_pointer(
G_OBJECT(view_delegate),
reinterpret_cast<gpointer*>(&(self->view_delegate)));

self->pan_started = false;
self->zoom_started = false;
self->rotate_started = false;

return self;
}

void fl_scrolling_manager_set_last_mouse_position(FlScrollingManager* self,
gdouble x,
gdouble y) {
self->last_x = x;
self->last_y = y;
}

void fl_scrolling_manager_handle_scroll_event(FlScrollingManager* self,
GdkEventScroll* event,
gint scale_factor) {
gdouble scroll_delta_x = 0.0, scroll_delta_y = 0.0;
if (event->direction == GDK_SCROLL_SMOOTH) {
scroll_delta_x = event->delta_x;
scroll_delta_y = event->delta_y;
} else if (event->direction == GDK_SCROLL_UP) {
scroll_delta_y = -1;
} else if (event->direction == GDK_SCROLL_DOWN) {
scroll_delta_y = 1;
} else if (event->direction == GDK_SCROLL_LEFT) {
scroll_delta_x = -1;
} else if (event->direction == GDK_SCROLL_RIGHT) {
scroll_delta_x = 1;
}

// The multiplier is taken from the Chromium source
// (ui/events/x/events_x_utils.cc).
const int kScrollOffsetMultiplier = 53;
scroll_delta_x *= kScrollOffsetMultiplier * scale_factor;
scroll_delta_y *= kScrollOffsetMultiplier * scale_factor;

if (gdk_device_get_source(gdk_event_get_source_device((GdkEvent*)event)) ==
GDK_SOURCE_TOUCHPAD) {
scroll_delta_x *= -1;
scroll_delta_y *= -1;
if (event->is_stop) {
fl_scrolling_view_delegate_send_pointer_pan_zoom_event(
self->view_delegate, event->time * kMicrosecondsPerMillisecond,
event->x * scale_factor, event->y * scale_factor, kPanZoomEnd,
self->pan_x, self->pan_y, 0, 0);
self->pan_started = FALSE;
} else {
if (!self->pan_started) {
self->pan_x = 0;
self->pan_y = 0;
fl_scrolling_view_delegate_send_pointer_pan_zoom_event(
self->view_delegate, event->time * kMicrosecondsPerMillisecond,
event->x * scale_factor, event->y * scale_factor, kPanZoomStart, 0,
0, 0, 0);
self->pan_started = TRUE;
}
self->pan_x += scroll_delta_x;
self->pan_y += scroll_delta_y;
fl_scrolling_view_delegate_send_pointer_pan_zoom_event(
self->view_delegate, event->time * kMicrosecondsPerMillisecond,
event->x * scale_factor, event->y * scale_factor, kPanZoomUpdate,
self->pan_x, self->pan_y, 1, 0);
}
} else {
self->last_x = event->x * scale_factor;
self->last_y = event->y * scale_factor;
fl_scrolling_view_delegate_send_mouse_pointer_event(
self->view_delegate,
FlutterPointerPhase::kMove /* arbitrary value, phase will be ignored as
this is a discrete scroll event */
,
event->time * kMicrosecondsPerMillisecond, event->x * scale_factor,
event->y * scale_factor, scroll_delta_x, scroll_delta_y, 0);
}
}

void fl_scrolling_manager_handle_rotation_begin(FlScrollingManager* self) {
self->rotate_started = true;
if (!self->zoom_started) {
self->scale = 1;
self->rotation = 0;
fl_scrolling_view_delegate_send_pointer_pan_zoom_event(
self->view_delegate, g_get_real_time(), self->last_x, self->last_y,
kPanZoomStart, 0, 0, 0, 0);
}
}

void fl_scrolling_manager_handle_rotation_update(FlScrollingManager* self,
gdouble rotation) {
self->rotation = rotation;
fl_scrolling_view_delegate_send_pointer_pan_zoom_event(
self->view_delegate, g_get_real_time(), self->last_x, self->last_y,
kPanZoomUpdate, 0, 0, self->scale, self->rotation);
}
void fl_scrolling_manager_handle_rotation_end(FlScrollingManager* self) {
self->rotate_started = false;
if (!self->zoom_started) {
fl_scrolling_view_delegate_send_pointer_pan_zoom_event(
self->view_delegate, g_get_real_time(), self->last_x, self->last_y,
kPanZoomEnd, 0, 0, 0, 0);
}
}

void fl_scrolling_manager_handle_zoom_begin(FlScrollingManager* self) {
self->zoom_started = true;
if (!self->rotate_started) {
self->scale = 1;
self->rotation = 0;
fl_scrolling_view_delegate_send_pointer_pan_zoom_event(
self->view_delegate, g_get_real_time(), self->last_x, self->last_y,
kPanZoomStart, 0, 0, 0, 0);
}
}
void fl_scrolling_manager_handle_zoom_update(FlScrollingManager* self,
gdouble scale) {
self->scale = scale;
fl_scrolling_view_delegate_send_pointer_pan_zoom_event(
self->view_delegate, g_get_real_time(), self->last_x, self->last_y,
kPanZoomUpdate, 0, 0, self->scale, self->rotation);
}
void fl_scrolling_manager_handle_zoom_end(FlScrollingManager* self) {
self->zoom_started = false;
if (!self->rotate_started) {
fl_scrolling_view_delegate_send_pointer_pan_zoom_event(
self->view_delegate, g_get_real_time(), self->last_x, self->last_y,
kPanZoomEnd, 0, 0, 0, 0);
}
}
Loading

0 comments on commit 178adb3

Please sign in to comment.