Support forward and back buttons (#164356)

Based on https://github.com/flutter/flutter/pull/163500 by @2bndy5
This commit is contained in:
Robert Ancell 2025-03-01 03:28:47 +13:00 committed by GitHub
parent 25b3a4743a
commit 933cb5da82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 286 additions and 92 deletions

View File

@ -27,6 +27,35 @@ struct _FlPointerManager {
G_DEFINE_TYPE(FlPointerManager, fl_pointer_manager, G_TYPE_OBJECT);
// 8 corresponds to mouse back button on both x11 and wayland
static constexpr guint kMouseButtonBack = 8;
// 9 corresponds to mouse forward button on both x11 and wayland
static constexpr guint kMouseButtonForward = 9;
// Convert a GDK button ID into a Flutter button ID
static gboolean get_mouse_button(guint gdk_button, int64_t* button) {
switch (gdk_button) {
case GDK_BUTTON_PRIMARY:
*button = kFlutterPointerButtonMousePrimary;
return TRUE;
case GDK_BUTTON_MIDDLE:
*button = kFlutterPointerButtonMouseMiddle;
return TRUE;
case GDK_BUTTON_SECONDARY:
*button = kFlutterPointerButtonMouseSecondary;
return TRUE;
case kMouseButtonBack:
*button = kFlutterPointerButtonMouseBack;
return TRUE;
case kMouseButtonForward:
*button = kFlutterPointerButtonMouseForward;
return TRUE;
default:
return FALSE;
}
}
// Generates a mouse pointer event if the pointer appears inside the window.
static void ensure_pointer_added(FlPointerManager* self,
guint event_time,
@ -79,9 +108,14 @@ gboolean fl_pointer_manager_handle_button_press(
FlutterPointerDeviceKind device_kind,
gdouble x,
gdouble y,
int64_t button) {
guint gdk_button) {
g_return_val_if_fail(FL_IS_POINTER_MANAGER(self), FALSE);
int64_t button;
if (!get_mouse_button(gdk_button, &button)) {
return FALSE;
}
ensure_pointer_added(self, event_time, device_kind, x, y);
// Drop the event if Flutter already thinks the button is down.
@ -112,9 +146,14 @@ gboolean fl_pointer_manager_handle_button_release(
FlutterPointerDeviceKind device_kind,
gdouble x,
gdouble y,
int64_t button) {
guint gdk_button) {
g_return_val_if_fail(FL_IS_POINTER_MANAGER(self), FALSE);
int64_t button;
if (!get_mouse_button(gdk_button, &button)) {
return FALSE;
}
// Drop the event if Flutter already thinks the button is up.
if ((self->button_state & button) == 0) {
return FALSE;

View File

@ -35,7 +35,7 @@ FlPointerManager* fl_pointer_manager_new(FlutterViewId view_id,
* @device_kind: kind of device generating the event.
* @x: x co-ordinate of event.
* @y: y co-ordinate of event.
* @button: button being pressed.
* @gdk_button: button being pressed.
*
* Returns %TRUE if this event was handled.
*/
@ -45,7 +45,7 @@ gboolean fl_pointer_manager_handle_button_press(
FlutterPointerDeviceKind device_kind,
gdouble x,
gdouble y,
int64_t button);
guint gdk_button);
/**
* fl_pointer_manager_handle_button_release:
@ -54,7 +54,7 @@ gboolean fl_pointer_manager_handle_button_press(
* @device_kind: kind of device generating the event.
* @x: x co-ordinate of event.
* @y: y co-ordinate of event.
* @button: button being released.
* @gdk_button: button being released.
*
* Returns %TRUE if this event was handled.
*/
@ -64,7 +64,7 @@ gboolean fl_pointer_manager_handle_button_release(
FlutterPointerDeviceKind device_kind,
gdouble x,
gdouble y,
int64_t button);
guint gdk_button);
/**
* fl_pointer_manager_handle_motion:

View File

@ -157,9 +157,9 @@ TEST(FlPointerManagerTest, EnterButtonPress) {
g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine);
fl_pointer_manager_handle_enter(manager, 1234, kFlutterPointerDeviceKindMouse,
1.0, 2.0);
fl_pointer_manager_handle_button_press(
manager, 1235, kFlutterPointerDeviceKindMouse, 4.0, 8.0,
kFlutterPointerButtonMousePrimary);
fl_pointer_manager_handle_button_press(manager, 1235,
kFlutterPointerDeviceKindMouse, 4.0,
8.0, GDK_BUTTON_PRIMARY);
EXPECT_EQ(pointer_events.size(), 2u);
@ -199,9 +199,9 @@ TEST(FlPointerManagerTest, NoEnterButtonPress) {
}));
g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine);
fl_pointer_manager_handle_button_press(
manager, 1234, kFlutterPointerDeviceKindMouse, 4.0, 8.0,
kFlutterPointerButtonMousePrimary);
fl_pointer_manager_handle_button_press(manager, 1234,
kFlutterPointerDeviceKindMouse, 4.0,
8.0, GDK_BUTTON_PRIMARY);
EXPECT_EQ(pointer_events.size(), 2u);
@ -221,7 +221,7 @@ TEST(FlPointerManagerTest, NoEnterButtonPress) {
EXPECT_EQ(pointer_events[1].view_id, 42);
}
TEST(FlPointerManagerTest, ButtonPressButtonRelease) {
TEST(FlPointerManagerTest, ButtonPressButtonReleasePrimary) {
g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project);
@ -242,12 +242,12 @@ TEST(FlPointerManagerTest, ButtonPressButtonRelease) {
}));
g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine);
fl_pointer_manager_handle_button_press(
manager, 1234, kFlutterPointerDeviceKindMouse, 4.0, 8.0,
kFlutterPointerButtonMousePrimary);
fl_pointer_manager_handle_button_release(
manager, 1235, kFlutterPointerDeviceKindMouse, 5.0, 9.0,
kFlutterPointerButtonMousePrimary);
fl_pointer_manager_handle_button_press(manager, 1234,
kFlutterPointerDeviceKindMouse, 4.0,
8.0, GDK_BUTTON_PRIMARY);
fl_pointer_manager_handle_button_release(manager, 1235,
kFlutterPointerDeviceKindMouse, 5.0,
9.0, GDK_BUTTON_PRIMARY);
EXPECT_EQ(pointer_events.size(), 3u);
@ -266,6 +266,184 @@ TEST(FlPointerManagerTest, ButtonPressButtonRelease) {
EXPECT_EQ(pointer_events[2].view_id, 42);
}
TEST(FlPointerManagerTest, ButtonPressButtonReleaseSecondary) {
g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project);
g_autoptr(GError) error = nullptr;
EXPECT_TRUE(fl_engine_start(engine, &error));
EXPECT_EQ(error, nullptr);
std::vector<FlutterPointerEvent> pointer_events;
fl_engine_get_embedder_api(engine)->SendPointerEvent = MOCK_ENGINE_PROC(
SendPointerEvent,
([&pointer_events](auto engine, const FlutterPointerEvent* events,
size_t events_count) {
for (size_t i = 0; i < events_count; i++) {
pointer_events.push_back(events[i]);
}
return kSuccess;
}));
g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine);
fl_pointer_manager_handle_button_press(manager, 1234,
kFlutterPointerDeviceKindMouse, 4.0,
8.0, GDK_BUTTON_SECONDARY);
fl_pointer_manager_handle_button_release(manager, 1235,
kFlutterPointerDeviceKindMouse, 5.0,
9.0, GDK_BUTTON_SECONDARY);
EXPECT_EQ(pointer_events.size(), 3u);
// Ignore first synthetic enter event
EXPECT_EQ(pointer_events[1].timestamp, 1234000u);
EXPECT_EQ(pointer_events[1].x, 4.0);
EXPECT_EQ(pointer_events[1].y, 8.0);
EXPECT_EQ(pointer_events[1].device_kind, kFlutterPointerDeviceKindMouse);
EXPECT_EQ(pointer_events[1].buttons, kFlutterPointerButtonMouseSecondary);
EXPECT_EQ(pointer_events[1].view_id, 42);
EXPECT_EQ(pointer_events[2].timestamp, 1235000u);
EXPECT_EQ(pointer_events[2].x, 5.0);
EXPECT_EQ(pointer_events[2].y, 9.0);
EXPECT_EQ(pointer_events[2].device_kind, kFlutterPointerDeviceKindMouse);
EXPECT_EQ(pointer_events[2].buttons, 0);
EXPECT_EQ(pointer_events[2].view_id, 42);
}
TEST(FlPointerManagerTest, ButtonPressButtonReleaseMiddle) {
g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project);
g_autoptr(GError) error = nullptr;
EXPECT_TRUE(fl_engine_start(engine, &error));
EXPECT_EQ(error, nullptr);
std::vector<FlutterPointerEvent> pointer_events;
fl_engine_get_embedder_api(engine)->SendPointerEvent = MOCK_ENGINE_PROC(
SendPointerEvent,
([&pointer_events](auto engine, const FlutterPointerEvent* events,
size_t events_count) {
for (size_t i = 0; i < events_count; i++) {
pointer_events.push_back(events[i]);
}
return kSuccess;
}));
g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine);
fl_pointer_manager_handle_button_press(manager, 1234,
kFlutterPointerDeviceKindMouse, 4.0,
8.0, GDK_BUTTON_MIDDLE);
fl_pointer_manager_handle_button_release(manager, 1235,
kFlutterPointerDeviceKindMouse, 5.0,
9.0, GDK_BUTTON_MIDDLE);
EXPECT_EQ(pointer_events.size(), 3u);
// Ignore first synthetic enter event
EXPECT_EQ(pointer_events[1].timestamp, 1234000u);
EXPECT_EQ(pointer_events[1].x, 4.0);
EXPECT_EQ(pointer_events[1].y, 8.0);
EXPECT_EQ(pointer_events[1].device_kind, kFlutterPointerDeviceKindMouse);
EXPECT_EQ(pointer_events[1].buttons, kFlutterPointerButtonMouseMiddle);
EXPECT_EQ(pointer_events[1].view_id, 42);
EXPECT_EQ(pointer_events[2].timestamp, 1235000u);
EXPECT_EQ(pointer_events[2].x, 5.0);
EXPECT_EQ(pointer_events[2].y, 9.0);
EXPECT_EQ(pointer_events[2].device_kind, kFlutterPointerDeviceKindMouse);
EXPECT_EQ(pointer_events[2].buttons, 0);
EXPECT_EQ(pointer_events[2].view_id, 42);
}
TEST(FlPointerManagerTest, ButtonPressButtonReleaseBack) {
g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project);
g_autoptr(GError) error = nullptr;
EXPECT_TRUE(fl_engine_start(engine, &error));
EXPECT_EQ(error, nullptr);
std::vector<FlutterPointerEvent> pointer_events;
fl_engine_get_embedder_api(engine)->SendPointerEvent = MOCK_ENGINE_PROC(
SendPointerEvent,
([&pointer_events](auto engine, const FlutterPointerEvent* events,
size_t events_count) {
for (size_t i = 0; i < events_count; i++) {
pointer_events.push_back(events[i]);
}
return kSuccess;
}));
// Forward button is 8 (no GDK define).
g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine);
fl_pointer_manager_handle_button_press(
manager, 1234, kFlutterPointerDeviceKindMouse, 4.0, 8.0, 8);
fl_pointer_manager_handle_button_release(
manager, 1235, kFlutterPointerDeviceKindMouse, 5.0, 9.0, 8);
EXPECT_EQ(pointer_events.size(), 3u);
// Ignore first synthetic enter event
EXPECT_EQ(pointer_events[1].timestamp, 1234000u);
EXPECT_EQ(pointer_events[1].x, 4.0);
EXPECT_EQ(pointer_events[1].y, 8.0);
EXPECT_EQ(pointer_events[1].device_kind, kFlutterPointerDeviceKindMouse);
EXPECT_EQ(pointer_events[1].buttons, kFlutterPointerButtonMouseBack);
EXPECT_EQ(pointer_events[1].view_id, 42);
EXPECT_EQ(pointer_events[2].timestamp, 1235000u);
EXPECT_EQ(pointer_events[2].x, 5.0);
EXPECT_EQ(pointer_events[2].y, 9.0);
EXPECT_EQ(pointer_events[2].device_kind, kFlutterPointerDeviceKindMouse);
EXPECT_EQ(pointer_events[2].buttons, 0);
EXPECT_EQ(pointer_events[2].view_id, 42);
}
TEST(FlPointerManagerTest, ButtonPressButtonReleaseForward) {
g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project);
g_autoptr(GError) error = nullptr;
EXPECT_TRUE(fl_engine_start(engine, &error));
EXPECT_EQ(error, nullptr);
std::vector<FlutterPointerEvent> pointer_events;
fl_engine_get_embedder_api(engine)->SendPointerEvent = MOCK_ENGINE_PROC(
SendPointerEvent,
([&pointer_events](auto engine, const FlutterPointerEvent* events,
size_t events_count) {
for (size_t i = 0; i < events_count; i++) {
pointer_events.push_back(events[i]);
}
return kSuccess;
}));
// Forward button is 9 (no GDK define).
g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine);
fl_pointer_manager_handle_button_press(
manager, 1234, kFlutterPointerDeviceKindMouse, 4.0, 8.0, 9);
fl_pointer_manager_handle_button_release(
manager, 1235, kFlutterPointerDeviceKindMouse, 5.0, 9.0, 9);
EXPECT_EQ(pointer_events.size(), 3u);
// Ignore first synthetic enter event
EXPECT_EQ(pointer_events[1].timestamp, 1234000u);
EXPECT_EQ(pointer_events[1].x, 4.0);
EXPECT_EQ(pointer_events[1].y, 8.0);
EXPECT_EQ(pointer_events[1].device_kind, kFlutterPointerDeviceKindMouse);
EXPECT_EQ(pointer_events[1].buttons, kFlutterPointerButtonMouseForward);
EXPECT_EQ(pointer_events[1].view_id, 42);
EXPECT_EQ(pointer_events[2].timestamp, 1235000u);
EXPECT_EQ(pointer_events[2].x, 5.0);
EXPECT_EQ(pointer_events[2].y, 9.0);
EXPECT_EQ(pointer_events[2].device_kind, kFlutterPointerDeviceKindMouse);
EXPECT_EQ(pointer_events[2].buttons, 0);
EXPECT_EQ(pointer_events[2].view_id, 42);
}
TEST(FlPointerManagerTest, ButtonPressButtonReleaseThreeButtons) {
g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project);
@ -288,21 +466,21 @@ TEST(FlPointerManagerTest, ButtonPressButtonReleaseThreeButtons) {
g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine);
// Press buttons 1-2-3, release 3-2-1
fl_pointer_manager_handle_button_press(
manager, 1234, kFlutterPointerDeviceKindMouse, 1.0, 2.0,
kFlutterPointerButtonMousePrimary);
fl_pointer_manager_handle_button_press(
manager, 1235, kFlutterPointerDeviceKindMouse, 3.0, 4.0,
kFlutterPointerButtonMouseSecondary);
fl_pointer_manager_handle_button_press(manager, 1234,
kFlutterPointerDeviceKindMouse, 1.0,
2.0, GDK_BUTTON_PRIMARY);
fl_pointer_manager_handle_button_press(manager, 1235,
kFlutterPointerDeviceKindMouse, 3.0,
4.0, GDK_BUTTON_SECONDARY);
fl_pointer_manager_handle_button_press(manager, 1236,
kFlutterPointerDeviceKindMouse, 5.0,
6.0, kFlutterPointerButtonMouseMiddle);
fl_pointer_manager_handle_button_release(
manager, 1237, kFlutterPointerDeviceKindMouse, 7.0, 8.0,
kFlutterPointerButtonMouseMiddle);
fl_pointer_manager_handle_button_release(
manager, 1238, kFlutterPointerDeviceKindMouse, 9.0, 10.0,
kFlutterPointerButtonMouseSecondary);
6.0, GDK_BUTTON_MIDDLE);
fl_pointer_manager_handle_button_release(manager, 1237,
kFlutterPointerDeviceKindMouse, 7.0,
8.0, GDK_BUTTON_MIDDLE);
fl_pointer_manager_handle_button_release(manager, 1238,
kFlutterPointerDeviceKindMouse, 9.0,
10.0, GDK_BUTTON_SECONDARY);
fl_pointer_manager_handle_button_release(
manager, 1239, kFlutterPointerDeviceKindMouse, 11.0, 12.0,
kFlutterPointerButtonMousePrimary);
@ -361,16 +539,16 @@ TEST(FlPointerManagerTest, ButtonPressButtonPressButtonRelease) {
}));
g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine);
fl_pointer_manager_handle_button_press(
manager, 1234, kFlutterPointerDeviceKindMouse, 4.0, 8.0,
kFlutterPointerButtonMousePrimary);
fl_pointer_manager_handle_button_press(manager, 1234,
kFlutterPointerDeviceKindMouse, 4.0,
8.0, GDK_BUTTON_PRIMARY);
// Ignore duplicate press
fl_pointer_manager_handle_button_press(
manager, 1234, kFlutterPointerDeviceKindMouse, 6.0, 10.0,
kFlutterPointerButtonMousePrimary);
fl_pointer_manager_handle_button_release(
manager, 1235, kFlutterPointerDeviceKindMouse, 5.0, 9.0,
kFlutterPointerButtonMousePrimary);
fl_pointer_manager_handle_button_press(manager, 1234,
kFlutterPointerDeviceKindMouse, 6.0,
10.0, GDK_BUTTON_PRIMARY);
fl_pointer_manager_handle_button_release(manager, 1235,
kFlutterPointerDeviceKindMouse, 5.0,
9.0, GDK_BUTTON_PRIMARY);
EXPECT_EQ(pointer_events.size(), 3u);
@ -410,16 +588,16 @@ TEST(FlPointerManagerTest, ButtonPressButtonReleaseButtonRelease) {
}));
g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine);
fl_pointer_manager_handle_button_press(
manager, 1234, kFlutterPointerDeviceKindMouse, 4.0, 8.0,
kFlutterPointerButtonMousePrimary);
fl_pointer_manager_handle_button_release(
manager, 1235, kFlutterPointerDeviceKindMouse, 5.0, 9.0,
kFlutterPointerButtonMousePrimary);
fl_pointer_manager_handle_button_press(manager, 1234,
kFlutterPointerDeviceKindMouse, 4.0,
8.0, GDK_BUTTON_PRIMARY);
fl_pointer_manager_handle_button_release(manager, 1235,
kFlutterPointerDeviceKindMouse, 5.0,
9.0, GDK_BUTTON_PRIMARY);
// Ignore duplicate release
fl_pointer_manager_handle_button_release(
manager, 1235, kFlutterPointerDeviceKindMouse, 6.0, 10.0,
kFlutterPointerButtonMousePrimary);
fl_pointer_manager_handle_button_release(manager, 1235,
kFlutterPointerDeviceKindMouse, 6.0,
10.0, GDK_BUTTON_PRIMARY);
EXPECT_EQ(pointer_events.size(), 3u);
@ -460,9 +638,9 @@ TEST(FlPointerManagerTest, NoButtonPressButtonRelease) {
g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine);
// Release without associated press, will be ignored
fl_pointer_manager_handle_button_release(
manager, 1235, kFlutterPointerDeviceKindMouse, 5.0, 9.0,
kFlutterPointerButtonMousePrimary);
fl_pointer_manager_handle_button_release(manager, 1235,
kFlutterPointerDeviceKindMouse, 5.0,
9.0, GDK_BUTTON_PRIMARY);
EXPECT_EQ(pointer_events.size(), 0u);
}
@ -541,14 +719,14 @@ TEST(FlPointerManagerTest, Drag) {
g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine);
fl_pointer_manager_handle_motion(manager, 1234,
kFlutterPointerDeviceKindMouse, 1.0, 2.0);
fl_pointer_manager_handle_button_press(
manager, 1235, kFlutterPointerDeviceKindMouse, 3.0, 4.0,
kFlutterPointerButtonMousePrimary);
fl_pointer_manager_handle_button_press(manager, 1235,
kFlutterPointerDeviceKindMouse, 3.0,
4.0, GDK_BUTTON_PRIMARY);
fl_pointer_manager_handle_motion(manager, 1236,
kFlutterPointerDeviceKindMouse, 5.0, 6.0);
fl_pointer_manager_handle_button_release(
manager, 1237, kFlutterPointerDeviceKindMouse, 7.0, 8.0,
kFlutterPointerButtonMousePrimary);
fl_pointer_manager_handle_button_release(manager, 1237,
kFlutterPointerDeviceKindMouse, 7.0,
8.0, GDK_BUTTON_PRIMARY);
fl_pointer_manager_handle_motion(manager, 1238,
kFlutterPointerDeviceKindMouse, 9.0, 10.0);
@ -605,14 +783,14 @@ TEST(FlPointerManagerTest, DeviceKind) {
g_autoptr(FlPointerManager) manager = fl_pointer_manager_new(42, engine);
fl_pointer_manager_handle_enter(manager, 1234,
kFlutterPointerDeviceKindTrackpad, 1.0, 2.0);
fl_pointer_manager_handle_button_press(
manager, 1235, kFlutterPointerDeviceKindTrackpad, 1.0, 2.0,
kFlutterPointerButtonMousePrimary);
fl_pointer_manager_handle_button_press(manager, 1235,
kFlutterPointerDeviceKindTrackpad, 1.0,
2.0, GDK_BUTTON_PRIMARY);
fl_pointer_manager_handle_motion(manager, 1238,
kFlutterPointerDeviceKindTrackpad, 3.0, 4.0);
fl_pointer_manager_handle_button_release(
manager, 1237, kFlutterPointerDeviceKindTrackpad, 3.0, 4.0,
kFlutterPointerButtonMousePrimary);
fl_pointer_manager_handle_button_release(manager, 1237,
kFlutterPointerDeviceKindTrackpad,
3.0, 4.0, GDK_BUTTON_PRIMARY);
fl_pointer_manager_handle_leave(manager, 1235,
kFlutterPointerDeviceKindTrackpad, 3.0, 4.0);

View File

@ -135,25 +135,6 @@ static FlutterPointerDeviceKind get_device_kind(GdkEvent* event) {
}
}
static gboolean get_mouse_button(GdkEvent* event, int64_t* button) {
guint event_button = 0;
gdk_event_get_button(event, &event_button);
switch (event_button) {
case GDK_BUTTON_PRIMARY:
*button = kFlutterPointerButtonMousePrimary;
return TRUE;
case GDK_BUTTON_MIDDLE:
*button = kFlutterPointerButtonMouseMiddle;
return TRUE;
case GDK_BUTTON_SECONDARY:
*button = kFlutterPointerButtonMouseSecondary;
return TRUE;
default:
return FALSE;
}
}
// Called when the mouse cursor changes.
static void cursor_changed_cb(FlView* self) {
FlMouseCursorHandler* handler =
@ -323,10 +304,8 @@ static gboolean button_press_event_cb(FlView* self,
return FALSE;
}
int64_t button;
if (!get_mouse_button(event, &button)) {
return FALSE;
}
guint button = 0;
gdk_event_get_button(event, &button);
gdouble x = 0.0, y = 0.0;
gdk_event_get_coords(event, &x, &y);
@ -345,10 +324,8 @@ static gboolean button_release_event_cb(FlView* self,
GdkEventButton* button_event) {
GdkEvent* event = reinterpret_cast<GdkEvent*>(button_event);
int64_t button;
if (!get_mouse_button(event, &button)) {
return FALSE;
}
guint button = 0;
gdk_event_get_button(event, &button);
gdouble x = 0.0, y = 0.0;
gdk_event_get_coords(event, &x, &y);