diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index b9a68fbdae449..2001d376048f1 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1444,6 +1444,8 @@ FILE: ../../../flutter/shell/platform/linux/fl_binary_codec_test.cc FILE: ../../../flutter/shell/platform/linux/fl_binary_messenger.cc FILE: ../../../flutter/shell/platform/linux/fl_binary_messenger_private.h FILE: ../../../flutter/shell/platform/linux/fl_binary_messenger_test.cc +FILE: ../../../flutter/shell/platform/linux/fl_clipping_view.cc +FILE: ../../../flutter/shell/platform/linux/fl_clipping_view.h FILE: ../../../flutter/shell/platform/linux/fl_dart_project.cc FILE: ../../../flutter/shell/platform/linux/fl_dart_project_private.h FILE: ../../../flutter/shell/platform/linux/fl_dart_project_test.cc @@ -1452,6 +1454,8 @@ FILE: ../../../flutter/shell/platform/linux/fl_engine_private.h FILE: ../../../flutter/shell/platform/linux/fl_engine_test.cc FILE: ../../../flutter/shell/platform/linux/fl_event_channel.cc FILE: ../../../flutter/shell/platform/linux/fl_event_channel_test.cc +FILE: ../../../flutter/shell/platform/linux/fl_gesture_helper.cc +FILE: ../../../flutter/shell/platform/linux/fl_gesture_helper.h FILE: ../../../flutter/shell/platform/linux/fl_gl_area.cc FILE: ../../../flutter/shell/platform/linux/fl_gl_area.h FILE: ../../../flutter/shell/platform/linux/fl_json_message_codec.cc @@ -1478,6 +1482,11 @@ FILE: ../../../flutter/shell/platform/linux/fl_mouse_cursor_plugin.cc FILE: ../../../flutter/shell/platform/linux/fl_mouse_cursor_plugin.h FILE: ../../../flutter/shell/platform/linux/fl_platform_plugin.cc FILE: ../../../flutter/shell/platform/linux/fl_platform_plugin.h +FILE: ../../../flutter/shell/platform/linux/fl_platform_views.cc +FILE: ../../../flutter/shell/platform/linux/fl_platform_views_plugin.cc +FILE: ../../../flutter/shell/platform/linux/fl_platform_views_plugin.h +FILE: ../../../flutter/shell/platform/linux/fl_platform_views_plugin_test.cc +FILE: ../../../flutter/shell/platform/linux/fl_platform_views_private.h FILE: ../../../flutter/shell/platform/linux/fl_plugin_registrar.cc FILE: ../../../flutter/shell/platform/linux/fl_plugin_registrar_private.h FILE: ../../../flutter/shell/platform/linux/fl_plugin_registry.cc @@ -1519,6 +1528,7 @@ FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_method_call. FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_method_codec.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_method_response.h +FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_platform_views.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_plugin_registrar.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_plugin_registry.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_standard_message_codec.h diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index b7ed3e4946d62..df2a25b6eb11f 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -55,6 +55,7 @@ _public_headers = [ "public/flutter_linux/fl_method_channel.h", "public/flutter_linux/fl_method_codec.h", "public/flutter_linux/fl_method_response.h", + "public/flutter_linux/fl_platform_views.h", "public/flutter_linux/fl_plugin_registrar.h", "public/flutter_linux/fl_plugin_registry.h", "public/flutter_linux/fl_standard_message_codec.h", @@ -91,9 +92,11 @@ source_set("flutter_linux_sources") { "fl_basic_message_channel.cc", "fl_binary_codec.cc", "fl_binary_messenger.cc", + "fl_clipping_view.cc", "fl_dart_project.cc", "fl_engine.cc", "fl_event_channel.cc", + "fl_gesture_helper.cc", "fl_gl_area.cc", "fl_json_message_codec.cc", "fl_json_method_codec.cc", @@ -105,6 +108,8 @@ source_set("flutter_linux_sources") { "fl_method_response.cc", "fl_mouse_cursor_plugin.cc", "fl_platform_plugin.cc", + "fl_platform_views.cc", + "fl_platform_views_plugin.cc", "fl_plugin_registrar.cc", "fl_plugin_registry.cc", "fl_renderer.cc", @@ -171,6 +176,7 @@ executable("flutter_linux_unittests") { "fl_method_channel_test.cc", "fl_method_codec_test.cc", "fl_method_response_test.cc", + "fl_platform_views_plugin_test.cc", "fl_standard_message_codec_test.cc", "fl_standard_method_codec_test.cc", "fl_string_codec_test.cc", @@ -178,6 +184,7 @@ executable("flutter_linux_unittests") { "testing/fl_test.cc", "testing/mock_engine.cc", "testing/mock_epoxy.cc", + "testing/mock_platform_views.cc", "testing/mock_renderer.cc", "testing/mock_text_input_plugin.cc", ] diff --git a/shell/platform/linux/fl_clipping_view.cc b/shell/platform/linux/fl_clipping_view.cc new file mode 100644 index 0000000000000..44b803c5dc785 --- /dev/null +++ b/shell/platform/linux/fl_clipping_view.cc @@ -0,0 +1,180 @@ +// 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_clipping_view.h" + +#include "flutter/shell/platform/embedder/embedder.h" + +struct _FlClippingView { + GtkEventBox parent_instance; + + GdkRectangle geometry; + GPtrArray* mutations; +}; + +G_DEFINE_TYPE(FlClippingView, fl_clipping_view, GTK_TYPE_EVENT_BOX) + +static void fl_clipping_view_dispose(GObject* gobject) { + FlClippingView* self = FL_CLIPPING_VIEW(gobject); + + g_clear_pointer(&self->mutations, g_ptr_array_unref); + + G_OBJECT_CLASS(fl_clipping_view_parent_class)->dispose(gobject); +} + +// Implements GtkWidget::size-allocate. +static void fl_clipping_view_size_allocate(GtkWidget* widget, + GtkAllocation* allocation) { + FlClippingView* self = FL_CLIPPING_VIEW(widget); + + gtk_widget_set_allocation(widget, allocation); + + if (gtk_widget_get_has_window(widget)) { + if (gtk_widget_get_realized(widget)) + gdk_window_move_resize(gtk_widget_get_window(widget), allocation->x, + allocation->y, allocation->width, + allocation->height); + } + + GtkWidget* child = gtk_bin_get_child(GTK_BIN(self)); + if (!child || !gtk_widget_get_visible(child)) + return; + + GtkAllocation child_allocation = self->geometry; + if (!gtk_widget_get_has_window(widget)) { + child_allocation.x += allocation->x; + child_allocation.y += allocation->y; + } + gtk_widget_size_allocate(child, &child_allocation); +} + +// Implements GtkWidget::draw. +static gboolean fl_clipping_view_draw(GtkWidget* widget, cairo_t* cr) { + FlClippingView* self = FL_CLIPPING_VIEW(widget); + + // We currently can only clip widgets that have no GdkWindow. + + cairo_save(cr); + if (self->mutations) { + for (guint i = 0; i < self->mutations->len; i++) { + FlutterPlatformViewMutation* mutation = + reinterpret_cast( + self->mutations->pdata[i]); + switch (mutation->type) { + case kFlutterPlatformViewMutationTypeOpacity: { + // opacitiy is applied in fl_clipping_view_reset (). + } break; + case kFlutterPlatformViewMutationTypeClipRect: { + cairo_rectangle(cr, mutation->clip_rect.left, mutation->clip_rect.top, + mutation->clip_rect.right - mutation->clip_rect.left, + mutation->clip_rect.bottom - mutation->clip_rect.top); + cairo_clip(cr); + } break; + case kFlutterPlatformViewMutationTypeClipRoundedRect: { + FlutterSize* top_left_radii = + &mutation->clip_rounded_rect.upper_left_corner_radius; + FlutterSize* top_right_radii = + &mutation->clip_rounded_rect.upper_right_corner_radius; + FlutterSize* bottom_left_radii = + &mutation->clip_rounded_rect.lower_left_corner_radius; + FlutterSize* bottom_right_radii = + &mutation->clip_rounded_rect.lower_right_corner_radius; + FlutterRect* rect = &mutation->clip_rounded_rect.rect; + cairo_move_to(cr, rect->left + top_left_radii->width, rect->top); + + cairo_line_to(cr, rect->right - top_right_radii->width, rect->top); + cairo_curve_to(cr, rect->right, rect->top, // + rect->right, rect->top + top_right_radii->height, + rect->right, rect->top + top_right_radii->height); + + cairo_line_to(cr, rect->right, + rect->bottom - bottom_right_radii->height); + cairo_curve_to(cr, rect->right, rect->bottom, + rect->right - bottom_right_radii->width, rect->bottom, + rect->right - bottom_right_radii->width, rect->bottom); + + cairo_line_to(cr, rect->left + bottom_left_radii->width, + rect->bottom); + cairo_curve_to(cr, rect->left, rect->bottom, // + rect->left, rect->bottom - bottom_left_radii->height, + rect->left, rect->bottom - bottom_left_radii->height); + + cairo_line_to(cr, rect->left, rect->top + top_left_radii->height); + cairo_curve_to(cr, rect->left, rect->top, + rect->left + top_left_radii->width, rect->top, + rect->left + top_left_radii->width, rect->top); + + cairo_close_path(cr); + cairo_clip(cr); + } break; + case kFlutterPlatformViewMutationTypeTransformation: { + cairo_matrix_t matrix = { + .xx = mutation->transformation.scaleX, + .yx = mutation->transformation.skewY, + .xy = mutation->transformation.skewX, + .yy = mutation->transformation.scaleY, + .x0 = mutation->transformation.transX, + .y0 = mutation->transformation.transY, + }; + cairo_transform(cr, &matrix); + } break; + } + } + } + cairo_translate(cr, -self->geometry.x, -self->geometry.y); + gboolean result = + GTK_WIDGET_CLASS(fl_clipping_view_parent_class)->draw(widget, cr); + cairo_restore(cr); + return result; +} + +static void fl_clipping_view_constructed(GObject* object) { + gtk_event_box_set_visible_window(GTK_EVENT_BOX(object), TRUE); +} + +static void fl_clipping_view_class_init(FlClippingViewClass* klass) { + GObjectClass* gobject_class = G_OBJECT_CLASS(klass); + gobject_class->constructed = fl_clipping_view_constructed; + gobject_class->dispose = fl_clipping_view_dispose; + + GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass); + widget_class->draw = fl_clipping_view_draw; + widget_class->size_allocate = fl_clipping_view_size_allocate; +} + +static void fl_clipping_view_init(FlClippingView* self) {} + +GtkWidget* fl_clipping_view_new() { + return GTK_WIDGET(g_object_new(fl_clipping_view_get_type(), nullptr)); +} + +void fl_clipping_view_reset(FlClippingView* self, + GtkWidget* child, + GdkRectangle* geometry, + GPtrArray* mutations) { + g_return_if_fail(FL_IS_CLIPPING_VIEW(self)); + g_return_if_fail(mutations != nullptr); + + GtkWidget* old_child = gtk_bin_get_child(GTK_BIN(self)); + if (old_child != child) { + if (old_child) { + gtk_container_remove(GTK_CONTAINER(self), old_child); + } + + g_object_ref(child); + gtk_container_add(GTK_CONTAINER(self), child); + } + self->geometry = *geometry; + g_clear_pointer(&self->mutations, g_ptr_array_unref); + self->mutations = g_ptr_array_ref(mutations); + + for (guint i = 0; i < mutations->len; i++) { + FlutterPlatformViewMutation* mutation = + reinterpret_cast( + self->mutations->pdata[i]); + if (mutation->type == kFlutterPlatformViewMutationTypeOpacity) { + gtk_widget_set_opacity(child, mutation->opacity); + } + } +} diff --git a/shell/platform/linux/fl_clipping_view.h b/shell/platform/linux/fl_clipping_view.h new file mode 100644 index 0000000000000..2c84a51675ec9 --- /dev/null +++ b/shell/platform/linux/fl_clipping_view.h @@ -0,0 +1,52 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_CLIPPING_VIEW_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_CLIPPING_VIEW_H_ + +#include + +#include + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FlClippingView, + fl_clipping_view, + FL, + CLIPPING_VIEW, + GtkEventBox) + +/** + * FlClippingView: + * + * #FlClippingView is a GTK widget that applies clipping mutations set by + * Framework side to child platform view. + */ + +/** + * fl_clipping_view_new: + * + * Creates a new #FlClippingView widget. + * + * Returns: the newly created #FlClippingView widget. + */ +GtkWidget* fl_clipping_view_new(); + +/** + * fl_clipping_view_reset: + * @clipping_view: an #FlClippingView. + * @child: widget to apply mutators. + * @geometry: geometry of widget. + * @mutations: the clipping mutations to be applied. + * + * Reset child widget and its mutations. + */ +void fl_clipping_view_reset(FlClippingView* clipping_view, + GtkWidget* child, + GdkRectangle* geometry, + GPtrArray* mutations); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_CLIPPING_VIEW_H_ diff --git a/shell/platform/linux/fl_gesture_helper.cc b/shell/platform/linux/fl_gesture_helper.cc new file mode 100644 index 0000000000000..8b118fca33f8f --- /dev/null +++ b/shell/platform/linux/fl_gesture_helper.cc @@ -0,0 +1,176 @@ +// 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_gesture_helper.h" + +struct _FlGestureHelper { + GObject parent_instance; + + GList* event_list; + GtkWidget* grabbed_widget; + GtkWidget* hover_widget; + int64_t pointer_id; + int32_t pressed_buttons; + bool end; +}; + +G_DEFINE_TYPE(FlGestureHelper, fl_gesture_helper, G_TYPE_OBJECT) + +static void fl_gesture_helper_dispose(GObject* object) { + FlGestureHelper* self = FL_GESTURE_HELPER(object); + + g_list_free_full(self->event_list, free_event); + self->event_list = nullptr; + + g_clear_object(&self->grabbed_widget); + g_clear_object(&self->hover_widget); + + G_OBJECT_CLASS(fl_gesture_helper_parent_class)->dispose(object); +} + +static void fl_gesture_helper_class_init(FlGestureHelperClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_gesture_helper_dispose; +} + +static void fl_gesture_helper_init(FlGestureHelper* self) {} + +FlGestureHelper* fl_gesture_helper_new() { + return FL_GESTURE_HELPER(g_object_new(fl_gesture_helper_get_type(), nullptr)); +} + +static void free_event(gpointer data) { + gdk_event_free(reinterpret_cast(data)); +} + +static void send_button_event(FlGestureHelper* self, + GdkEvent* event, + GtkWidget* widget) { + if (event->type == GDK_BUTTON_PRESS) { + self->end = false; + self->pressed_buttons |= 1 << event->button.button; + } else if (event->type == GDK_BUTTON_RELEASE) { + self->end = true; + self->pressed_buttons &= ~(1 << event->button.button); + } else { + return; + } + + GdkWindow* window = gtk_widget_get_window(widget); + gint origin_x, origin_y; + gdk_window_get_origin(window, &origin_x, &origin_y); + GdkEvent* new_event = gdk_event_copy(event); + GdkEventButton* button = &new_event->button; + button->x = event->button.x_root - origin_x; + button->y = event->button.y_root - origin_y; + gtk_widget_event(widget, new_event); + gdk_event_free(new_event); +} + +static void send_motion_event(FlGestureHelper* self, + GdkEvent* event, + GtkWidget* widget) { + if (self->end) + return; + + GdkWindow* window = gtk_widget_get_window(widget); + gint origin_x, origin_y; + gdk_window_get_origin(window, &origin_x, &origin_y); + + GdkEvent* new_event = gdk_event_copy(event); + GdkEventMotion* motion = &new_event->motion; + motion->x = event->motion.x_root - origin_x; + motion->y = event->motion.y_root - origin_y; + gtk_widget_event(widget, new_event); + gdk_event_free(new_event); +} + +static void send_scroll_event(FlGestureHelper* self, + GdkEvent* event, + GtkWidget* widget) { + GdkWindow* window = gtk_widget_get_window(widget); + gint origin_x, origin_y; + gdk_window_get_origin(window, &origin_x, &origin_y); + + GdkEvent* new_event = gdk_event_copy(event); + GdkEventScroll* scroll = &new_event->scroll; + scroll->x = event->scroll.x_root - origin_x; + scroll->y = event->scroll.y_root - origin_y; + gtk_widget_event(widget, new_event); + gdk_event_free(new_event); +} + +static void clear_state(FlGestureHelper* self) { + g_clear_object(&self->grabbed_widget); + g_list_free_full(self->event_list, free_event); + self->event_list = nullptr; +} + +void fl_gesture_helper_button_press(FlGestureHelper* self, GdkEvent* event) { + clear_state(self); + self->pointer_id++; + + self->event_list = g_list_append(self->event_list, gdk_event_copy(event)); +} + +void fl_gesture_helper_button_release(FlGestureHelper* self, GdkEvent* event) { + if (self->grabbed_widget) { + send_button_event(self, event, self->grabbed_widget); + + clear_state(self); + } else { + self->event_list = g_list_append(self->event_list, gdk_event_copy(event)); + } +} + +void fl_gesture_helper_button_motion(FlGestureHelper* self, GdkEvent* event) { + if (self->grabbed_widget) { + // We have grabbed the widget, we directly distribute the event to the + // widget. + if (!self->end) { + send_motion_event(self, event, self->grabbed_widget); + } + } else { + if (self->pressed_buttons) { + // Or if there are some mouse buttons pressed, delay the event. + self->event_list = g_list_append(self->event_list, gdk_event_copy(event)); + } else if (self->hover_widget) { + // Or mouse is hovering. + send_motion_event(self, event, self->hover_widget); + } + } +} + +void fl_gesture_helper_scroll(FlGestureHelper* self, GdkEvent* event) { + if (self->hover_widget) { + // If mouse is hovering. + send_scroll_event(self, event, self->hover_widget); + } +} + +void fl_gesture_helper_accept_gesture(FlGestureHelper* self, + GtkWidget* widget, + int64_t pointer_id) { + g_set_object(&self->grabbed_widget, widget); + + // send stored events to grabbed widget. + for (GList* event = self->event_list; event; event = event->next) { + GdkEvent* gdk_event = reinterpret_cast(event->data); + if (gdk_event->type == GDK_BUTTON_PRESS || + gdk_event->type == GDK_BUTTON_RELEASE) { + send_button_event(self, gdk_event, self->grabbed_widget); + } else if (gdk_event->type == GDK_MOTION_NOTIFY) { + if (self->pressed_buttons && !self->end) { + send_motion_event(self, gdk_event, self->grabbed_widget); + } + } + } +} + +void fl_gesture_helper_enter(FlGestureHelper* self, GtkWidget* widget) { + g_set_object(&self->hover_widget, widget); +} + +void fl_gesture_helper_exit(FlGestureHelper* self, GtkWidget* widget) { + g_clear_object(&self->hover_widget); +} diff --git a/shell/platform/linux/fl_gesture_helper.h b/shell/platform/linux/fl_gesture_helper.h new file mode 100644 index 0000000000000..80145d94ffc88 --- /dev/null +++ b/shell/platform/linux/fl_gesture_helper.h @@ -0,0 +1,111 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_GESTURE_HELPER_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_GESTURE_HELPER_H_ + +#include + +#include + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FlGestureHelper, + fl_gesture_helper, + FL, + GESTURE_HELPER, + GObject) + +/** + * FlGestureHelper: + * + * #FlGestureHelper captures and redistributes mouse events to embedded platform + * views. + */ + +/** + * fl_gesture_helper_new: + * + * Creates a new #FlGestureHelper object. + * + * Returns: a new #FlGestureHelper. + */ +FlGestureHelper* fl_gesture_helper_new(); + +/** + * fl_gesture_helper_button_press: + * @gesture_helper: an #FlGestureHelper. + * @event: a #GdkEventButton event. + * + * Redistrubutes @event to a embedded platform view. + */ +void fl_gesture_helper_button_press(FlGestureHelper* gesture_helper, + GdkEvent* event); + +/** + * fl_gesture_helper_button_release: + * @gesture_helper: an #FlGestureHelper. + * @event: a #GdkEventButton event. + * + * Redistributes @event to a embeeded platform view. + */ +void fl_gesture_helper_button_release(FlGestureHelper* gesture_helper, + GdkEvent* event); + +/** + * fl_gesture_helper_button_motion: + * @gesture_helper: an #FlGestureHelper. + * @event: a #GdkEventMotion event. + * + * Redistributes a #GdkEventMotion event to embedded platform views. + */ +void fl_gesture_helper_button_motion(FlGestureHelper* gesture_helper, + GdkEvent* event); + +/** + * fl_gesture_helper_scroll: + * @gesture_helper: an #FlGestureHelper. + * @event: a #GdkEventScroll event. + * + * Redistributes a #GdkEventScroll event to embedded platform views. + */ +void fl_gesture_helper_scroll(FlGestureHelper* gesture_helper, GdkEvent* event); + +/** + * fl_gesture_helper_accept_gesture: + * @gesture_helper: an #FlGestureHelper. + * @widget: the widget to distribute current gesture to. + * @pointer_id: touch sequence id. + * + * Distributes all delayed and following pointer events of current gesture to + * @widget. + */ +void fl_gesture_helper_accept_gesture(FlGestureHelper* gesture_helper, + GtkWidget* widget, + int64_t pointer_id); + +/** + * fl_gesture_helper_enter: + * @gesture_helper: an #FlGestureHelper. + * @widget: the widget that a mouse pointer enters into. + * + * Notify that a mouse pointer enters into @widget, and distribute following + * motion events to @widget. + */ +void fl_gesture_helper_enter(FlGestureHelper* gesture_helper, + GtkWidget* widget); + +/** + * fl_gesture_helper_exit: + * @gesture_helper: an #FlGestureHelper. + * @widget: the widget that a mouse pointer exits from. + * + * Notify that a mouse pointer exits from @widget, and stop distributing motion + * events to @widget. + */ +void fl_gesture_helper_exit(FlGestureHelper* gesture_helper, GtkWidget* widget); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_GESTURE_HELPER_H_ diff --git a/shell/platform/linux/fl_platform_views.cc b/shell/platform/linux/fl_platform_views.cc new file mode 100644 index 0000000000000..b1b2a788d4918 --- /dev/null +++ b/shell/platform/linux/fl_platform_views.cc @@ -0,0 +1,77 @@ +// 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/public/flutter_linux/fl_platform_views.h" + +#include "flutter/shell/platform/linux/fl_platform_views_private.h" + +#include + +// Added here to stop the compiler from optimizing this function away. +G_MODULE_EXPORT GType fl_platform_view_get_type(); + +typedef struct { + GtkTextDirection direction; +} FlPlatformViewPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE(FlPlatformView, fl_platform_view, G_TYPE_OBJECT) + +static void fl_platform_view_class_init(FlPlatformViewClass* klass) {} + +static void fl_platform_view_init(FlPlatformView* self) {} + +G_MODULE_EXPORT GtkWidget* fl_platform_view_get_view(FlPlatformView* self) { + g_return_val_if_fail(FL_IS_PLATFORM_VIEW(self), nullptr); + + GtkWidget* widget = FL_PLATFORM_VIEW_GET_CLASS(self)->get_view(self); + if (!widget || !GTK_IS_WIDGET(widget)) { + g_critical("fl_platform_view::get_view should return GtkWidget"); + return nullptr; + } + + FlPlatformViewPrivate* priv = static_cast( + fl_platform_view_get_instance_private(self)); + gtk_widget_set_direction(widget, priv->direction); + + return widget; +} + +void fl_platform_view_set_direction(FlPlatformView* self, + GtkTextDirection direction) { + FlPlatformViewPrivate* priv = static_cast( + fl_platform_view_get_instance_private(self)); + priv->direction = direction; +} + +// Added here to stop the compiler from optimizing this function away. +G_MODULE_EXPORT GType fl_platform_view_factory_get_type(); + +G_DEFINE_INTERFACE(FlPlatformViewFactory, + fl_platform_view_factory, + G_TYPE_OBJECT) + +static void fl_platform_view_factory_default_init( + FlPlatformViewFactoryInterface* iface) {} + +G_MODULE_EXPORT FlPlatformView* fl_platform_view_factory_create_platform_view( + FlPlatformViewFactory* self, + int64_t view_identifier, + FlValue* args) { + g_return_val_if_fail(FL_IS_PLATFORM_VIEW_FACTORY(self), nullptr); + + return FL_PLATFORM_VIEW_FACTORY_GET_IFACE(self)->create_platform_view( + self, view_identifier, args); +} + +G_MODULE_EXPORT FlMessageCodec* +fl_platform_view_factory_get_create_arguments_codec( + FlPlatformViewFactory* self) { + g_return_val_if_fail(FL_IS_PLATFORM_VIEW_FACTORY(self), nullptr); + + if (!FL_PLATFORM_VIEW_FACTORY_GET_IFACE(self)->get_create_arguments_codec) + return nullptr; + + return FL_PLATFORM_VIEW_FACTORY_GET_IFACE(self)->get_create_arguments_codec( + self); +} diff --git a/shell/platform/linux/fl_platform_views_plugin.cc b/shell/platform/linux/fl_platform_views_plugin.cc new file mode 100644 index 0000000000000..35df82113cf0f --- /dev/null +++ b/shell/platform/linux/fl_platform_views_plugin.cc @@ -0,0 +1,380 @@ +// 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_platform_views_plugin.h" + +#include +#include + +#include "flutter/shell/platform/linux/fl_gesture_helper.h" +#include "flutter/shell/platform/linux/fl_platform_views_private.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" + +static constexpr char kChannelName[] = "flutter/platform_views"; +static constexpr char kBadArgumentsError[] = "Bad Arguments"; +static constexpr char kBadPreconditionError[] = "Illegal State"; +static constexpr char kCreateMethod[] = "create"; +static constexpr char kDisposeMethod[] = "dispose"; +static constexpr char kAcceptGestureMethod[] = "acceptGesture"; +static constexpr char kRejectGestureMethod[] = "rejectGesture"; +static constexpr char kSetDirectionMethod[] = "setDirection"; +static constexpr char kEnterMethod[] = "enter"; +static constexpr char kExitMethod[] = "exit"; + +struct _FlPlatformViewsPlugin { + GObject parent_instance; + + FlMethodChannel* channel; + + // gesture_helper is not owned here. + FlGestureHelper* gesture_helper; + + GHashTable* factories; // string -> FlPlatformViewFactory* + + GHashTable* platform_views; // int -> FlPlatformView* +}; + +G_DEFINE_TYPE(FlPlatformViewsPlugin, fl_platform_views_plugin, G_TYPE_OBJECT) + +// Sends the method call response to Flutter Framework. +static void send_response(FlMethodCall* method_call, + FlMethodResponse* response) { + g_autoptr(GError) error = nullptr; + if (!fl_method_call_respond(method_call, response, &error)) { + g_warning("Failed to send method call response: %s", error->message); + } +} + +// Creates a new platform view. +static FlMethodResponse* platform_views_create(FlPlatformViewsPlugin* self, + FlValue* args) { + if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Argument map missing or malformed", nullptr)); + } + + FlValue* id_value = fl_value_lookup_string(args, "id"); + if (id_value == nullptr || fl_value_get_type(id_value) != FL_VALUE_TYPE_INT) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Missing platform view id", nullptr)); + } + int64_t id = fl_value_get_int(id_value); + + FlValue* view_type_value = fl_value_lookup_string(args, "viewType"); + if (view_type_value == nullptr || + fl_value_get_type(view_type_value) != FL_VALUE_TYPE_STRING) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Missing platform view viewType", nullptr)); + } + const gchar* view_type = fl_value_get_string(view_type_value); + + FlValue* direction_value = fl_value_lookup_string(args, "direction"); + if (direction_value == nullptr || + fl_value_get_type(direction_value) != FL_VALUE_TYPE_INT) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Missing platform view direction", nullptr)); + } + GtkTextDirection direction = + static_cast(fl_value_get_int(direction_value)); + + FlValue* params_value = fl_value_lookup_string(args, "params"); + + FlPlatformView* platform_view = + fl_platform_views_plugin_get_platform_view(self, id); + if (platform_view) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Platform view id already exists", nullptr)); + } + + FlPlatformViewFactory* factory = + FL_PLATFORM_VIEW_FACTORY(g_hash_table_lookup(self->factories, view_type)); + if (!factory) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "View type does not exist", nullptr)); + } + + g_autoptr(FlMessageCodec) codec = + fl_platform_view_factory_get_create_arguments_codec(factory); + FlValue* creation_params = nullptr; + if (codec && params_value) { + const uint8_t* creation_params_bytes = + fl_value_get_uint8_list(params_value); + size_t creation_params_length = fl_value_get_length(params_value); + g_autoptr(GBytes) bytes = + g_bytes_new_static(creation_params_bytes, creation_params_length); + + g_autoptr(GError) error = nullptr; + creation_params = fl_message_codec_decode_message(codec, bytes, &error); + if (!creation_params) { + g_warning("Failed to decode creation params, error message: %s", + error->message); + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Creation params cannot be parsed", nullptr)); + } + } + + platform_view = fl_platform_view_factory_create_platform_view( + factory, id, creation_params); + if (!FL_IS_PLATFORM_VIEW(platform_view)) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Invalid platform view", nullptr)); + } + fl_platform_view_set_direction(platform_view, direction); + + g_hash_table_insert(self->platform_views, GINT_TO_POINTER(id), platform_view); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Called when Flutter considers this gesture applied to a platform view. +static FlMethodResponse* platform_views_accept_gesture( + FlPlatformViewsPlugin* self, + FlValue* args) { + if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST || + fl_value_get_length(args) != 2) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Argument list missing or malformed", nullptr)); + } + + if (!self->gesture_helper) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadPreconditionError, "Gesture helper destroyed", nullptr)); + } + + int64_t view_id = fl_value_get_int(fl_value_get_list_value(args, 0)); + int64_t pointer_id = fl_value_get_int(fl_value_get_list_value(args, 1)); + FlPlatformView* platform_view = + fl_platform_views_plugin_get_platform_view(self, view_id); + if (!platform_view) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Platform view not found", nullptr)); + } + + GtkWidget* widget = fl_platform_view_get_view(platform_view); + if (widget) + fl_gesture_helper_accept_gesture(self->gesture_helper, widget, pointer_id); + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Reject gesture captured by the platform view. +static FlMethodResponse* platform_views_reject_gesture( + FlPlatformViewsPlugin* self, + FlValue* args) { + if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST || + fl_value_get_length(args) != 1) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Argument list missing or malformed", nullptr)); + } + + int64_t view_id = fl_value_get_int(fl_value_get_list_value(args, 0)); + FlPlatformView* platform_view = + fl_platform_views_plugin_get_platform_view(self, view_id); + if (!platform_view) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Platform view not found", nullptr)); + } + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Set GtkTextDirection of platform view. +static FlMethodResponse* platform_views_set_direction( + FlPlatformViewsPlugin* self, + FlValue* args) { + if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST || + fl_value_get_length(args) != 2) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Argument list missing or malformed", nullptr)); + } + + int64_t view_id = fl_value_get_int(fl_value_get_list_value(args, 0)); + GtkTextDirection direction = static_cast( + fl_value_get_int(fl_value_get_list_value(args, 1))); + + FlPlatformView* platform_view = + fl_platform_views_plugin_get_platform_view(self, view_id); + if (!platform_view) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Platform view not found", nullptr)); + } + + fl_platform_view_set_direction(platform_view, direction); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Mark that a mouse pointer has entered into the platform view. +static FlMethodResponse* platform_views_enter(FlPlatformViewsPlugin* self, + FlValue* args) { + if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST || + fl_value_get_length(args) != 1) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Argument list missing or malformed", nullptr)); + } + + if (!self->gesture_helper) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadPreconditionError, "Gesture helper destroyed", nullptr)); + } + + int64_t view_id = fl_value_get_int(fl_value_get_list_value(args, 0)); + FlPlatformView* platform_view = + fl_platform_views_plugin_get_platform_view(self, view_id); + if (!platform_view) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Platform view not found", nullptr)); + } + + GtkWidget* widget = fl_platform_view_get_view(platform_view); + if (widget) + fl_gesture_helper_enter(self->gesture_helper, widget); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Mark that a mouse pointer has exited from the platform view. +static FlMethodResponse* platform_views_exit(FlPlatformViewsPlugin* self, + FlValue* args) { + if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST || + fl_value_get_length(args) != 1) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Argument list missing or malformed", nullptr)); + } + + if (!self->gesture_helper) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadPreconditionError, "Gesture helper destroyed", nullptr)); + } + + int64_t view_id = fl_value_get_int(fl_value_get_list_value(args, 0)); + FlPlatformView* platform_view = + fl_platform_views_plugin_get_platform_view(self, view_id); + if (!platform_view) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Platform view not found", nullptr)); + } + + GtkWidget* widget = fl_platform_view_get_view(platform_view); + if (widget) + fl_gesture_helper_exit(self->gesture_helper, widget); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Disposes the platform view. +static FlMethodResponse* platform_views_dispose(FlPlatformViewsPlugin* self, + FlValue* args) { + if (fl_value_get_type(args) != FL_VALUE_TYPE_INT) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Argument map missing or malformed", nullptr)); + } + + int64_t id = fl_value_get_int(args); + + if (!g_hash_table_remove(self->platform_views, GINT_TO_POINTER(id))) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Platform view does not exist", nullptr)); + } + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Called when a method call is received from Flutter. +static void method_call_cb(FlMethodChannel* channel, + FlMethodCall* method_call, + gpointer user_data) { + FlPlatformViewsPlugin* self = FL_PLATFORM_VIEWS_PLUGIN(user_data); + + const gchar* method = fl_method_call_get_name(method_call); + FlValue* args = fl_method_call_get_args(method_call); + + g_autoptr(FlMethodResponse) response = nullptr; + if (strcmp(method, kCreateMethod) == 0) { + response = platform_views_create(self, args); + } else if (strcmp(method, kDisposeMethod) == 0) { + response = platform_views_dispose(self, args); + } else if (strcmp(method, kAcceptGestureMethod) == 0) { + response = platform_views_accept_gesture(self, args); + } else if (strcmp(method, kRejectGestureMethod) == 0) { + response = platform_views_reject_gesture(self, args); + } else if (strcmp(method, kSetDirectionMethod) == 0) { + response = platform_views_set_direction(self, args); + } else if (strcmp(method, kEnterMethod) == 0) { + response = platform_views_enter(self, args); + } else if (strcmp(method, kExitMethod) == 0) { + response = platform_views_exit(self, args); + } else { + response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); + } + + if (response != nullptr) { + send_response(method_call, response); + } +} + +static void fl_platform_views_plugin_dispose(GObject* object) { + FlPlatformViewsPlugin* self = FL_PLATFORM_VIEWS_PLUGIN(object); + + g_clear_object(&self->channel); + g_hash_table_destroy(self->factories); + g_hash_table_destroy(self->platform_views); + self->factories = self->platform_views = nullptr; + + if (self->gesture_helper != nullptr) { + g_object_remove_weak_pointer( + G_OBJECT(self), reinterpret_cast(&(self->gesture_helper))); + self->gesture_helper = nullptr; + } + + G_OBJECT_CLASS(fl_platform_views_plugin_parent_class)->dispose(object); +} + +static void fl_platform_views_plugin_class_init( + FlPlatformViewsPluginClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_platform_views_plugin_dispose; +} + +static void fl_platform_views_plugin_init(FlPlatformViewsPlugin* self) {} + +FlPlatformViewsPlugin* fl_platform_views_plugin_new( + FlBinaryMessenger* messenger, + FlGestureHelper* gesture_helper) { + g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); + + FlPlatformViewsPlugin* self = FL_PLATFORM_VIEWS_PLUGIN( + g_object_new(fl_platform_views_plugin_get_type(), nullptr)); + + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + self->channel = + fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self, + nullptr); + + self->gesture_helper = gesture_helper; + g_object_add_weak_pointer( + G_OBJECT(self), reinterpret_cast(&(self->gesture_helper))); + + self->factories = + g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref); + self->platform_views = g_hash_table_new_full(g_direct_hash, g_direct_equal, + nullptr, g_object_unref); + + return self; +} + +gboolean fl_platform_views_plugin_register_view_factory( + FlPlatformViewsPlugin* self, + FlPlatformViewFactory* factory, + const gchar* view_type) { + g_object_ref(factory); + return g_hash_table_insert(self->factories, g_strdup(view_type), factory); +} + +FlPlatformView* fl_platform_views_plugin_get_platform_view( + FlPlatformViewsPlugin* self, + int64_t view_identifier) { + return reinterpret_cast(g_hash_table_lookup( + self->platform_views, GINT_TO_POINTER(view_identifier))); +} diff --git a/shell/platform/linux/fl_platform_views_plugin.h b/shell/platform/linux/fl_platform_views_plugin.h new file mode 100644 index 0000000000000..e462af12efac1 --- /dev/null +++ b/shell/platform/linux/fl_platform_views_plugin.h @@ -0,0 +1,76 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_PLATFORM_VIEWS_PLUGIN_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_PLATFORM_VIEWS_PLUGIN_H_ + +#include "flutter/shell/platform/linux/fl_gesture_helper.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_platform_views.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FlPlatformViewsPlugin, + fl_platform_views_plugin, + FL, + PLATFORM_VIEWS_PLUGIN, + GObject); + +/** + * FlPlatformViewsPlugin: + * + * #FlPlatformViewsPlugin is a plugin that implements the shell side + * of SystemChannels.platform_views from the Flutter services library. This also + * manages shell-side platform views registration, processes platform views + * construction and destruction request from Framework side, transmit gesture + * events to widgets. + */ + +/** + * fl_platform_views_plugin_new: + * @messenger: (transfer-none): an #FlBinaryMessenger. + * @gesture_helper: (transfer-none): an #FlGestureHelper for accepting gestures + * by Dart side. + * + * Creates a new plugin that implements SystemChannels.platform_views from the + * Flutter services library. + * + * Returns: a new #FlPlatformViewsPlugin. + */ +FlPlatformViewsPlugin* fl_platform_views_plugin_new( + FlBinaryMessenger* messenger, + FlGestureHelper* gesture_helper); + +/** + * fl_platform_views_plugin_register_view_factory: + * @plugin: an #FlPlatformViewsPlugin. + * @factory: (transfer-none): the view factory that will be registered. + * @view_type: A unique identifier for the factory. The Dart code of the Flutter + * app can use this identifier to request creation of a #GtkWidget + * by the registered factory. + * + * Register platform view factory. + * + * Returns: %TRUE if view facotory has been successfully registered, %FALSE if + * @view_type has been occupied. + */ +gboolean fl_platform_views_plugin_register_view_factory( + FlPlatformViewsPlugin* plugin, + FlPlatformViewFactory* factory, + const gchar* view_type); + +/** + * fl_platform_views_plugin_get_platform_view: + * @plugin: an #FlPlatformViewsPlugin. + * @view_identifier: the identifier of platform view. + * + * Returns: an #FlPlatformView, or %NULL if @view_identifier is unknown. + */ +FlPlatformView* fl_platform_views_plugin_get_platform_view( + FlPlatformViewsPlugin* plugin, + int64_t view_identifier); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_PLATFORM_VIEWS_PLUGIN_H_ diff --git a/shell/platform/linux/fl_platform_views_plugin_test.cc b/shell/platform/linux/fl_platform_views_plugin_test.cc new file mode 100644 index 0000000000000..459b6ea7e40d9 --- /dev/null +++ b/shell/platform/linux/fl_platform_views_plugin_test.cc @@ -0,0 +1,206 @@ +// 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_platform_views_plugin.h" + +#include "gtest/gtest.h" + +#include "flutter/shell/platform/linux/fl_binary_messenger_private.h" +#include "flutter/shell/platform/linux/fl_engine_private.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_message_codec.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" +#include "flutter/shell/platform/linux/testing/fl_test.h" +#include "flutter/shell/platform/linux/testing/mock_platform_views.h" + +static void invoke_response_cb(GObject* source_object, + GAsyncResult* res, + gpointer user_data) { + g_main_loop_quit(reinterpret_cast(user_data)); +} + +// Called when a the test engine notifies us that a response a sent. +static void response_cb(FlBinaryMessenger* messenger, + const gchar* channel, + GBytes* message, + FlBinaryMessengerResponseHandle* response_handle, + gpointer user_data) { + fl_binary_messenger_send_response(messenger, response_handle, nullptr, + nullptr); + + g_main_loop_quit(static_cast(user_data)); +} + +static void invoke_method(GMainLoop* loop, + FlBinaryMessenger* messenger, + const gchar* channel_name, + const gchar* method_name, + FlValue* args, + GAsyncReadyCallback cb) { + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(FlMethodChannel) channel = fl_method_channel_new( + messenger, "test/standard-method", FL_METHOD_CODEC(codec)); + g_autoptr(FlValue) invoke_args = fl_value_new_list(); + fl_value_append_take(invoke_args, fl_value_new_string(channel_name)); + fl_value_append_take(invoke_args, fl_value_new_string(method_name)); + fl_value_append_take(invoke_args, args); + fl_method_channel_invoke_method(channel, "InvokeMethod", invoke_args, nullptr, + cb, loop); +} + +static void create_platform_view(GMainLoop* loop, + FlBinaryMessenger* messenger, + int64_t view_id, + const gchar* view_type, + FlValue* args, + GAsyncReadyCallback cb) { + FlValue* create_args = fl_value_new_map(); + fl_value_set_string_take(create_args, "id", fl_value_new_int(view_id)); + fl_value_set_string_take(create_args, "viewType", + fl_value_new_string(view_type)); + fl_value_set_string_take(create_args, "direction", + fl_value_new_int(GTK_TEXT_DIR_LTR)); + if (args) + fl_value_set_string_take(create_args, "params", args); + invoke_method(loop, messenger, "flutter/platform_views", "create", + create_args, cb); +} + +static void dispose_platform_view(GMainLoop* loop, + FlBinaryMessenger* messenger, + int64_t view_id, + GAsyncReadyCallback cb) { + invoke_method(loop, messenger, "flutter/platform_views", "dispose", + fl_value_new_int(view_id), cb); +} + +GtkWidget* get_view(FlPlatformView* self) { + return nullptr; +} + +static FlPlatformView* expected_platform_view; +static constexpr int64_t expected_view_identifier = 1; + +TEST(FlPlatformViewsPluginTest, CreatePlatformViewWithUnknownViewType) { + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + + g_autoptr(FlEngine) engine = make_mock_engine(); + g_autoptr(FlBinaryMessenger) messenger = fl_binary_messenger_new(engine); + g_autoptr(FlGestureHelper) gesture_helper = fl_gesture_helper_new(); + g_autoptr(FlPlatformViewsPlugin) plugin = + fl_platform_views_plugin_new(messenger, gesture_helper); + + create_platform_view(loop, messenger, 1, "unknown_view_type", nullptr, + invoke_response_cb); + + // Blocks here until invoke_response_cb is called. + g_main_loop_run(loop); + + EXPECT_EQ(nullptr, fl_platform_views_plugin_get_platform_view( + plugin, expected_view_identifier)); +} + +static FlPlatformView* create_platform_view_with_no_codec( + FlPlatformViewFactory* factory, + int64_t view_identifier, + FlValue* args) { + EXPECT_EQ(view_identifier, expected_view_identifier); + EXPECT_EQ(args, nullptr); + + return expected_platform_view = + FL_PLATFORM_VIEW(fl_mock_platform_view_new(get_view)); +} + +TEST(FlPlatformViewsPluginTest, CreatePlatformViewWithNoCodec) { + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + + g_autoptr(FlEngine) engine = make_mock_engine(); + g_autoptr(FlBinaryMessenger) messenger = fl_binary_messenger_new(engine); + + fl_binary_messenger_set_message_handler_on_channel( + messenger, "test/responses", response_cb, loop, nullptr); + + g_autoptr(FlGestureHelper) gesture_helper = fl_gesture_helper_new(); + g_autoptr(FlPlatformViewsPlugin) plugin = + fl_platform_views_plugin_new(messenger, gesture_helper); + FlMockViewFactory* factory = + fl_mock_view_factory_new(create_platform_view_with_no_codec, nullptr); + fl_platform_views_plugin_register_view_factory( + plugin, FL_PLATFORM_VIEW_FACTORY(factory), "some_view_type"); + + expected_platform_view = nullptr; + create_platform_view(loop, messenger, expected_view_identifier, + "some_view_type", nullptr, nullptr); + + // Blocks here until flutter/platform_views::create is called. + g_main_loop_run(loop); + + EXPECT_EQ(expected_platform_view, fl_platform_views_plugin_get_platform_view( + plugin, expected_view_identifier)); + + dispose_platform_view(loop, messenger, expected_view_identifier, + invoke_response_cb); + + // Blocks here until flutter/platform_views::dispose is called. + g_main_loop_run(loop); +} + +static FlPlatformView* create_platform_view_with_codec( + FlPlatformViewFactory* factory, + int64_t view_identifier, + FlValue* args) { + EXPECT_EQ(view_identifier, expected_view_identifier); + EXPECT_EQ(fl_value_get_type(args), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(args), "Hello World!"); + + return expected_platform_view = + FL_PLATFORM_VIEW(fl_mock_platform_view_new(get_view)); +} + +static FlMessageCodec* get_create_arguments_codec( + FlPlatformViewFactory* factory) { + return FL_MESSAGE_CODEC(fl_standard_message_codec_new()); +} + +TEST(FlPlatformViewsPluginTest, CreatePlatformViewWithCodec) { + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + + g_autoptr(GError) error = nullptr; + g_autoptr(FlEngine) engine = make_mock_engine(); + g_autoptr(FlBinaryMessenger) messenger = fl_binary_messenger_new(engine); + + fl_binary_messenger_set_message_handler_on_channel( + messenger, "test/responses", response_cb, loop, nullptr); + + g_autoptr(FlGestureHelper) gesture_helper = fl_gesture_helper_new(); + g_autoptr(FlPlatformViewsPlugin) plugin = + fl_platform_views_plugin_new(messenger, gesture_helper); + g_autoptr(FlMockViewFactory) factory = fl_mock_view_factory_new( + create_platform_view_with_codec, get_create_arguments_codec); + g_autoptr(FlMessageCodec) codec = + FL_MESSAGE_CODEC(fl_standard_message_codec_new()); + fl_platform_views_plugin_register_view_factory( + plugin, FL_PLATFORM_VIEW_FACTORY(factory), "some_view_type"); + + expected_platform_view = nullptr; + g_autoptr(FlValue) args = fl_value_new_string("Hello World!"); + g_autoptr(GBytes) bytes = + fl_message_codec_encode_message(codec, args, &error); + EXPECT_EQ(error, nullptr); + create_platform_view(loop, messenger, expected_view_identifier, + "some_view_type", + fl_value_new_uint8_list_from_bytes(bytes), nullptr); + + // Blocks here until invoke_method_response is called. + g_main_loop_run(loop); + + EXPECT_EQ(expected_platform_view, fl_platform_views_plugin_get_platform_view( + plugin, expected_view_identifier)); + + dispose_platform_view(loop, messenger, expected_view_identifier, + invoke_response_cb); + + // Blocks here until flutter/platform_views::dispose is called. + g_main_loop_run(loop); +} diff --git a/shell/platform/linux/fl_platform_views_private.h b/shell/platform/linux/fl_platform_views_private.h new file mode 100644 index 0000000000000..a63bde3bfdec6 --- /dev/null +++ b/shell/platform/linux/fl_platform_views_private.h @@ -0,0 +1,24 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_PLATFORM_VIEWS_PRIVATE_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_PLATFORM_VIEWS_PRIVATE_H_ + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_platform_views.h" + +G_BEGIN_DECLS + +/** + * fl_platform_view_set_direction: + * @platform_view: an #FlPlatformView. + * @direction: new #GtkTextDirection for platform view. + * + * Set text direction. + */ +void fl_platform_view_set_direction(FlPlatformView* platform_view, + GtkTextDirection direction); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_PLATFORM_VIEWS_PRIVATE_H_ diff --git a/shell/platform/linux/fl_plugin_registrar.cc b/shell/platform/linux/fl_plugin_registrar.cc index 5207497b2fb30..4b211d22beaf3 100644 --- a/shell/platform/linux/fl_plugin_registrar.cc +++ b/shell/platform/linux/fl_plugin_registrar.cc @@ -4,6 +4,7 @@ #include "flutter/shell/platform/linux/public/flutter_linux/fl_plugin_registrar.h" #include "flutter/shell/platform/linux/fl_plugin_registrar_private.h" +#include "flutter/shell/platform/linux/fl_view_private.h" #include @@ -71,3 +72,19 @@ G_MODULE_EXPORT FlView* fl_plugin_registrar_get_view(FlPluginRegistrar* self) { return self->view; } + +G_MODULE_EXPORT void fl_plugin_registrar_register_view_factory( + FlPluginRegistrar* self, + FlPlatformViewFactory* factory, + const gchar* view_type) { + g_return_if_fail(FL_IS_PLUGIN_REGISTRAR(self)); + + if (self->view) { + FlPlatformViewsPlugin* plugin = + fl_view_get_platform_views_plugin(self->view); + fl_platform_views_plugin_register_view_factory(plugin, factory, view_type); + } else { + g_warning("Cannot register view factory before FlView is initialized"); + g_object_unref(factory); + } +} diff --git a/shell/platform/linux/fl_renderer.h b/shell/platform/linux/fl_renderer.h index 545f7b8f584d7..638f75621e4ee 100644 --- a/shell/platform/linux/fl_renderer.h +++ b/shell/platform/linux/fl_renderer.h @@ -53,15 +53,6 @@ struct _FlRendererClass { GdkGLContext** resource, GError** error); - /** - * Virtual method called when Flutter needs OpenGL proc address. - * @renderer: an #FlRenderer. - * @name: proc name. - * - * Returns: OpenGL proc address. - */ - void* (*get_proc_address)(); - /** * Virtual method called when Flutter needs a backing store for a specific * #FlutterLayer. diff --git a/shell/platform/linux/fl_renderer_gl.cc b/shell/platform/linux/fl_renderer_gl.cc index 9a657f290f497..07bd078b3bdce 100644 --- a/shell/platform/linux/fl_renderer_gl.cc +++ b/shell/platform/linux/fl_renderer_gl.cc @@ -5,6 +5,7 @@ #include "flutter/shell/platform/linux/fl_renderer_gl.h" #include "flutter/shell/platform/linux/fl_backing_store_provider.h" +#include "flutter/shell/platform/linux/fl_platform_views_plugin.h" #include "flutter/shell/platform/linux/fl_view_private.h" struct _FlRendererGL { @@ -109,7 +110,32 @@ static gboolean fl_renderer_gl_present_layers(FlRenderer* renderer, reinterpret_cast(framebuffer->user_data)); } break; case kFlutterLayerContentTypePlatformView: { - // Currently unsupported. + FlPlatformViewsPlugin* plugin = fl_view_get_platform_views_plugin(view); + FlPlatformView* platform_view = + fl_platform_views_plugin_get_platform_view( + plugin, layer->platform_view->identifier); + GtkWidget* widget = fl_platform_view_get_view(platform_view); + if (!widget) { + continue; + } + GdkRectangle geometry = { + .x = static_cast(layer->offset.x), + .y = static_cast(layer->offset.y), + .width = static_cast(layer->size.width), + .height = static_cast(layer->size.height), + }; + + // Makes a copy of mutation array from embedder API so we can use it out + // of this method. + g_autoptr(GPtrArray) mutations = + g_ptr_array_new_full(layer->platform_view->mutations_count, g_free); + for (size_t i = 0; i < layer->platform_view->mutations_count; i++) { + FlutterPlatformViewMutation* mutation = + g_new(FlutterPlatformViewMutation, 1); + *mutation = *layer->platform_view->mutations[i]; + g_ptr_array_add(mutations, mutation); + } + fl_view_add_widget(view, widget, &geometry, mutations); } break; } } diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc index 8371d1848849f..e4b8627c1f9f3 100644 --- a/shell/platform/linux/fl_view.cc +++ b/shell/platform/linux/fl_view.cc @@ -6,13 +6,19 @@ #include "flutter/shell/platform/linux/fl_view_private.h" +#include #include +#include +#include #include "flutter/shell/platform/linux/fl_accessibility_plugin.h" +#include "flutter/shell/platform/linux/fl_clipping_view.h" #include "flutter/shell/platform/linux/fl_engine_private.h" +#include "flutter/shell/platform/linux/fl_gesture_helper.h" #include "flutter/shell/platform/linux/fl_key_event_plugin.h" #include "flutter/shell/platform/linux/fl_mouse_cursor_plugin.h" #include "flutter/shell/platform/linux/fl_platform_plugin.h" +#include "flutter/shell/platform/linux/fl_platform_views_plugin.h" #include "flutter/shell/platform/linux/fl_plugin_registrar_private.h" #include "flutter/shell/platform/linux/fl_renderer_gl.h" #include "flutter/shell/platform/linux/fl_text_input_plugin.h" @@ -43,24 +49,29 @@ struct _FlView { FlMouseCursorPlugin* mouse_cursor_plugin; FlPlatformPlugin* platform_plugin; FlTextInputPlugin* text_input_plugin; + FlPlatformViewsPlugin* platform_views_plugin; + + FlGestureHelper* gesture_helper; GList* gl_area_list; GList* used_area_list; + // FlClippingView keyed by GtkWidget + GHashTable* current_clipping_views; + // FlClippingView keyed by GtkWidget + GHashTable* last_clipping_views; + GtkWidget* event_box; + // GtkWidgets currently on screen. GList* children_list; + // GtkWidgets on screen in next frame. GList* pending_children_list; // Tracks whether mouse pointer is inside the view. gboolean pointer_inside; }; -typedef struct _FlViewChild { - GtkWidget* widget; - GdkRectangle geometry; -} FlViewChild; - enum { PROP_FLUTTER_PROJECT = 1, PROP_LAST }; static void fl_view_plugin_registry_iface_init( @@ -189,6 +200,8 @@ static void fl_view_constructed(GObject* object) { self->renderer = FL_RENDERER(fl_renderer_gl_new()); self->engine = fl_engine_new(self->project, self->renderer); + self->gesture_helper = fl_gesture_helper_new(); + fl_engine_set_update_semantics_node_handler( self->engine, fl_view_update_semantics_node_cb, self, nullptr); @@ -200,6 +213,13 @@ static void fl_view_constructed(GObject* object) { fl_key_event_plugin_new(messenger, self->text_input_plugin); self->mouse_cursor_plugin = fl_mouse_cursor_plugin_new(messenger, self); self->platform_plugin = fl_platform_plugin_new(messenger); + self->platform_views_plugin = + fl_platform_views_plugin_new(messenger, self->gesture_helper); + + self->current_clipping_views = g_hash_table_new_full( + g_direct_hash, g_direct_equal, nullptr, g_object_unref); + self->last_clipping_views = g_hash_table_new_full( + g_direct_hash, g_direct_equal, nullptr, g_object_unref); self->event_box = gtk_event_box_new(); gtk_widget_set_parent(self->event_box, GTK_WIDGET(self)); @@ -284,9 +304,16 @@ static void fl_view_dispose(GObject* object) { g_clear_object(&self->mouse_cursor_plugin); g_clear_object(&self->platform_plugin); g_clear_object(&self->text_input_plugin); + g_clear_object(&self->platform_views_plugin); + g_clear_object(&self->gesture_helper); g_list_free_full(self->gl_area_list, g_object_unref); self->gl_area_list = nullptr; + g_clear_pointer(&self->children_list, g_list_free); + g_clear_pointer(&self->pending_children_list, g_list_free); + g_clear_pointer(&self->current_clipping_views, g_hash_table_unref); + g_clear_pointer(&self->last_clipping_views, g_hash_table_unref); + G_OBJECT_CLASS(fl_view_parent_class)->dispose(object); } @@ -327,52 +354,6 @@ static void fl_view_realize(GtkWidget* widget) { } } -static void fl_view_get_preferred_width(GtkWidget* widget, - gint* minimum, - gint* natural) { - FlView* self = FL_VIEW(widget); - gint child_min, child_nat; - - *minimum = 0; - *natural = 0; - - for (GList* iterator = self->children_list; iterator; - iterator = iterator->next) { - FlViewChild* child = reinterpret_cast(iterator->data); - - if (!gtk_widget_get_visible(child->widget)) - continue; - - gtk_widget_get_preferred_width(child->widget, &child_min, &child_nat); - - *minimum = MAX(*minimum, child->geometry.x + child_min); - *natural = MAX(*natural, child->geometry.x + child_nat); - } -} - -static void fl_view_get_preferred_height(GtkWidget* widget, - gint* minimum, - gint* natural) { - FlView* self = FL_VIEW(widget); - gint child_min, child_nat; - - *minimum = 0; - *natural = 0; - - for (GList* iterator = self->children_list; iterator; - iterator = iterator->next) { - FlViewChild* child = reinterpret_cast(iterator->data); - - if (!gtk_widget_get_visible(child->widget)) - continue; - - gtk_widget_get_preferred_height(child->widget, &child_min, &child_nat); - - *minimum = MAX(*minimum, child->geometry.y + child_min); - *natural = MAX(*natural, child->geometry.y + child_nat); - } -} - // Implements GtkWidget::size-allocate. static void fl_view_size_allocate(GtkWidget* widget, GtkAllocation* allocation) { @@ -387,35 +368,29 @@ static void fl_view_size_allocate(GtkWidget* widget, allocation->height); } + GtkAllocation default_child_allocation = { + .x = 0, + .y = 0, + .width = allocation->width, + .height = allocation->height, + }; + for (GList* iterator = self->children_list; iterator; iterator = iterator->next) { - FlViewChild* child = reinterpret_cast(iterator->data); - if (!gtk_widget_get_visible(child->widget)) + GtkWidget* child = reinterpret_cast(iterator->data); + if (!gtk_widget_get_visible(child)) continue; - GtkAllocation child_allocation = child->geometry; - GtkRequisition child_requisition; - gtk_widget_get_preferred_size(child->widget, &child_requisition, NULL); - + GtkAllocation child_allocation = default_child_allocation; if (!gtk_widget_get_has_window(widget)) { child_allocation.x += allocation->x; child_allocation.y += allocation->y; } - if (child_allocation.width == 0 && child_allocation.height == 0) { - child_allocation.width = allocation->width; - child_allocation.height = allocation->height; - } - - gtk_widget_size_allocate(child->widget, &child_allocation); + gtk_widget_size_allocate(child, &child_allocation); } - GtkAllocation event_box_allocation = { - .x = 0, - .y = 0, - .width = allocation->width, - .height = allocation->height, - }; + GtkAllocation event_box_allocation = default_child_allocation; if (!gtk_widget_get_has_window(self->event_box)) { event_box_allocation.x += allocation->x; event_box_allocation.y += allocation->y; @@ -450,6 +425,9 @@ static gboolean event_box_button_press_event(GtkWidget* widget, return FALSE; } + fl_gesture_helper_button_press(view->gesture_helper, + reinterpret_cast(event)); + if (!gtk_widget_has_focus(GTK_WIDGET(view))) { gtk_widget_grab_focus(GTK_WIDGET(view)); } @@ -460,6 +438,8 @@ static gboolean event_box_button_press_event(GtkWidget* widget, static gboolean event_box_button_release_event(GtkWidget* widget, GdkEventButton* event, FlView* view) { + fl_gesture_helper_button_release(view->gesture_helper, + reinterpret_cast(event)); return fl_view_send_pointer_button_event(view, event); } @@ -483,6 +463,9 @@ static gboolean event_box_scroll_event(GtkWidget* widget, return FALSE; } + fl_gesture_helper_scroll(view->gesture_helper, + reinterpret_cast(event)); + // The multiplier is taken from the Chromium source // (ui/events/x/events_x_utils.cc). const int kScrollOffsetMultiplier = 53; @@ -524,6 +507,9 @@ static gboolean event_box_motion_notify_event(GtkWidget* widget, check_pointer_inside(view, reinterpret_cast(event)); + fl_gesture_helper_button_motion(view->gesture_helper, + reinterpret_cast(event)); + gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(view)); fl_engine_send_mouse_pointer_event( view->engine, view->button_state != 0 ? kMove : kHover, @@ -582,39 +568,24 @@ static gboolean fl_view_key_release_event(GtkWidget* widget, return fl_key_event_plugin_send_key_event(self->key_event_plugin, event); } -static void fl_view_put(FlView* self, - GtkWidget* widget, - GdkRectangle* geometry) { - FlViewChild* child = g_new(FlViewChild, 1); - child->widget = widget; - child->geometry = *geometry; - +static void fl_view_put(FlView* self, GtkWidget* widget) { gtk_widget_set_parent(widget, GTK_WIDGET(self)); - self->children_list = g_list_append(self->children_list, child); + self->children_list = g_list_append(self->children_list, widget); } static void fl_view_add(GtkContainer* container, GtkWidget* widget) { - GdkRectangle geometry = { - .x = 0, - .y = 0, - .width = 0, - .height = 0, - }; - fl_view_put(FL_VIEW(container), widget, &geometry); + fl_view_put(FL_VIEW(container), widget); } static void fl_view_remove(GtkContainer* container, GtkWidget* widget) { FlView* self = FL_VIEW(container); for (GList* iterator = self->children_list; iterator; iterator = iterator->next) { - FlViewChild* child = reinterpret_cast(iterator->data); - if (child->widget == widget) { - g_object_ref(widget); + GtkWidget* child = reinterpret_cast(iterator->data); + if (child == widget) { + g_object_ref(widget); // children_list does not own widgets. gtk_widget_unparent(widget); - self->children_list = g_list_remove_link(self->children_list, iterator); - g_list_free(iterator); - g_free(child); - + self->children_list = g_list_delete_link(self->children_list, iterator); break; } } @@ -631,8 +602,8 @@ static void fl_view_forall(GtkContainer* container, FlView* self = FL_VIEW(container); for (GList* iterator = self->children_list; iterator; iterator = iterator->next) { - FlViewChild* child = reinterpret_cast(iterator->data); - (*callback)(child->widget, callback_data); + GtkWidget* child = reinterpret_cast(iterator->data); + (*callback)(child, callback_data); } if (include_internals) { @@ -666,8 +637,6 @@ static void fl_view_class_init(FlViewClass* klass) { GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass); widget_class->realize = fl_view_realize; - widget_class->get_preferred_width = fl_view_get_preferred_width; - widget_class->get_preferred_height = fl_view_get_preferred_height; widget_class->size_allocate = fl_view_size_allocate; widget_class->key_press_event = fl_view_key_press_event; widget_class->key_release_event = fl_view_key_release_event; @@ -706,25 +675,20 @@ G_MODULE_EXPORT FlEngine* fl_view_get_engine(FlView* view) { return view->engine; } +FlPlatformViewsPlugin* fl_view_get_platform_views_plugin(FlView* self) { + g_return_val_if_fail(FL_IS_VIEW(self), FALSE); + return self->platform_views_plugin; +} + void fl_view_begin_frame(FlView* view) { g_return_if_fail(FL_IS_VIEW(view)); FlView* self = FL_VIEW(view); self->used_area_list = self->gl_area_list; - g_list_free_full(self->pending_children_list, g_free); - self->pending_children_list = nullptr; + g_clear_pointer(&self->pending_children_list, g_list_free); } -static void fl_view_add_pending_child(FlView* self, - GtkWidget* widget, - GdkRectangle* geometry) { - FlViewChild* child = g_new(FlViewChild, 1); - child->widget = widget; - if (geometry) - child->geometry = *geometry; - else - child->geometry = {0, 0, 0, 0}; - +static void fl_view_add_pending_child(FlView* self, GtkWidget* child) { self->pending_children_list = g_list_append(self->pending_children_list, child); } @@ -744,64 +708,65 @@ void fl_view_add_gl_area(FlView* view, } gtk_widget_show(GTK_WIDGET(area)); - fl_view_add_pending_child(view, GTK_WIDGET(area), nullptr); + fl_view_add_pending_child(view, GTK_WIDGET(area)); fl_gl_area_queue_render(area, texture); } void fl_view_add_widget(FlView* view, GtkWidget* widget, - GdkRectangle* geometry) { - gtk_widget_show(widget); - fl_view_add_pending_child(view, widget, geometry); -} - -GList* find_child(GList* list, GtkWidget* widget) { - for (GList* i = list; i; i = i->next) { - FlViewChild* child = reinterpret_cast(i->data); - if (child && child->widget == widget) - return i; + GdkRectangle* geometry, + GPtrArray* mutations) { + FlClippingView* clipping_view; + if (g_hash_table_contains(view->last_clipping_views, widget)) { + clipping_view = reinterpret_cast( + g_hash_table_lookup(view->last_clipping_views, widget)); + g_hash_table_steal(view->last_clipping_views, widget); + } else { + clipping_view = FL_CLIPPING_VIEW(fl_clipping_view_new()); } - return nullptr; + g_hash_table_insert(view->current_clipping_views, widget, clipping_view); + fl_clipping_view_reset(clipping_view, widget, geometry, mutations); + + gtk_widget_show_all(GTK_WIDGET(clipping_view)); + fl_view_add_pending_child(view, GTK_WIDGET(clipping_view)); } -void fl_view_end_frame(FlView* view) { - for (GList* pending_child = view->pending_children_list; pending_child; +void fl_view_end_frame(FlView* self) { + for (GList* pending_child = self->pending_children_list; pending_child; pending_child = pending_child->next) { - FlViewChild* pending_view_child = - reinterpret_cast(pending_child->data); - GList* child = find_child(view->children_list, pending_view_child->widget); - + GtkWidget* pending_view_child = + reinterpret_cast(pending_child->data); + GList* child = g_list_find(self->children_list, pending_view_child); if (child) { // existing child - g_free(child->data); - child->data = nullptr; + // We remove existing children in children_list, so children_list will + // finally contains all children to be removed. + self->children_list = g_list_delete_link(self->children_list, child); } else { // newly added child - gtk_widget_set_parent(pending_view_child->widget, GTK_WIDGET(view)); + gtk_widget_set_parent(pending_view_child, GTK_WIDGET(self)); } } - for (GList* child = view->children_list; child; child = child->next) { - FlViewChild* view_child = reinterpret_cast(child->data); - if (view_child) { - // removed child - g_object_ref(view_child->widget); - gtk_widget_unparent(view_child->widget); - g_free(view_child); - child->data = nullptr; - } + for (GList* child = self->children_list; child; child = child->next) { + GtkWidget* view_child = reinterpret_cast(child->data); + // removed child + g_object_ref(view_child); + gtk_widget_unparent(view_child); } - g_list_free(view->children_list); - view->children_list = view->pending_children_list; - view->pending_children_list = nullptr; + g_clear_pointer(&self->children_list, g_list_free); + std::swap(self->children_list, self->pending_children_list); + + g_hash_table_remove_all(self->last_clipping_views); + std::swap(self->last_clipping_views, self->current_clipping_views); struct _ReorderData data = { - .parent_window = gtk_widget_get_window(GTK_WIDGET(view)), + .parent_window = gtk_widget_get_window(GTK_WIDGET(self)), .last_window = nullptr, }; - gtk_container_forall(GTK_CONTAINER(view), fl_view_reorder_forall, &data); + gtk_container_forall(GTK_CONTAINER(self), fl_view_reorder_forall, &data); - gtk_widget_queue_draw(GTK_WIDGET(view)); + gtk_widget_queue_draw(GTK_WIDGET(self)); } diff --git a/shell/platform/linux/fl_view_private.h b/shell/platform/linux/fl_view_private.h index 693c937231528..5985503f46175 100644 --- a/shell/platform/linux/fl_view_private.h +++ b/shell/platform/linux/fl_view_private.h @@ -8,6 +8,17 @@ #include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h" #include "flutter/shell/platform/linux/fl_gl_area.h" +#include "flutter/shell/platform/linux/fl_platform_views_plugin.h" + +/** + * fl_view_get_platform_views_plugin(FlView* view); + * @view: an #FlView. + * + * Get #FlPlatformViewsPlugin related to @view. + * + * Returns: an #FlPlatformViewsPlugin. + */ +FlPlatformViewsPlugin* fl_view_get_platform_views_plugin(FlView* view); /** * fl_view_begin_frame: @@ -37,12 +48,14 @@ void fl_view_add_gl_area(FlView* view, * @view: an #FlView. * @widget: a #GtkWidget. * @geometry: geometry of the widget. + * @mutations: the clipping mutations applied to the widget. * * Append a #GtkWidget at top of stacked children of #FlView. */ void fl_view_add_widget(FlView* view, GtkWidget* widget, - GdkRectangle* geometry); + GdkRectangle* geometry, + GPtrArray* mutations); /** * fl_view_end_frame: diff --git a/shell/platform/linux/public/flutter_linux/fl_platform_views.h b/shell/platform/linux/public/flutter_linux/fl_platform_views.h new file mode 100644 index 0000000000000..0023ba497d94f --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_platform_views.h @@ -0,0 +1,224 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_PLATFORM_VIEWS_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_PLATFORM_VIEWS_H_ + +#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +#include "fl_message_codec.h" +#include "fl_value.h" + +G_BEGIN_DECLS + +G_DECLARE_DERIVABLE_TYPE(FlPlatformView, + fl_platform_view, + FL, + PLATFORM_VIEW, + GObject) + +/** + * FlPlatformView: + * + * #FlPlatformView is an abstract class that wraps a #GtkWidget for embedding in + * the Flutter hierarchy. + * + * The following example shows how to implement an #FlPlatformView. + * |[ + * // Type definition, destructor, init and class_init are omitted. + * struct _WebViewPlatformView { // extends FlPlatformView + * FlPlatformView parent_instance; + * int64_t view_identifier; + * WebKitWebView *webview; // holds reference to your widget. + * }; + * + * G_DEFINE_TYPE(WebViewPlatformView, + * webview_platform_view, + * fl_platform_view_get_type ()) + * + * static GtkWidget * + * webview_plaform_view_get_view (FlPlatformView *platform_view) { + * // Simply return the #GtkWidget you created in constructor. + * // DO NOT create widgets in this function, which will be called + * // every frame. + * WebViewPlatformView *self = WEBVIEW_PLATFORM_VIEW (platform_view); + * return GTK_WIDGET (self->webview); + * } + * + * // Constructor should be called by your custom #FlPlatformViewFactory. + * WebViewPlatformView * + * webview_platform_view_new (FlBinaryMessenger *messenger, + * int64_t view_identifier, + * FlValue *args) { + * // Initializes your #GtkWidget, and stores it in #FlPlatformView. + * WebKitWebView *webview = webview_web_view_new (); + * + * WebViewPlatformView *view = WEBVIEW_PLATFORM_VIEW ( + * g_object_new (webview_platform_view_get_type (), NULL)); + * view->webview = webview; + * return view; + * } + * ]| + */ + +struct _FlPlatformViewClass { + GObjectClass parent_class; + + /** + * Virtual method called when Flutter needs the #GtkWidget for embedding. + * @platform_view: an #FlPlatformView. + * + * Returns: (allow-none): an #GtkWidget or %NULL if no widgets can be + * provided. + */ + GtkWidget* (*get_view)(FlPlatformView* platform_view); +}; + +/** + * fl_platform_view_get_view: + * @platform_view: an #FlPlatformView. + * + * Returns: (allow-none): an #GtkWidget or %NULL if no widgets can be provided. + */ +GtkWidget* fl_platform_view_get_view(FlPlatformView* platform_view); + +G_DECLARE_INTERFACE(FlPlatformViewFactory, + fl_platform_view_factory, + FL, + PLATFORM_VIEW_FACTORY, + GObject) + +/** + * FlPlatformViewFactory: + * + * #FlPlatformViewFactory vends #FlPlatformView objects for embedding. + * + * The following example shows how to implement an #FlPlatformViewFactory. + * In common cases, #FlPlatformViewFactory may not hold references to your + * own #FlPlatformView. Flutter will manage life-cycle of platform views + * created by your factory. + * + * For header of your subclass: + * |[ + * // platform_view_factory.h + * G_DECLARE_FINAL_TYPE(WebViewFactory, + * webview_factory, + * WEBVIEW, + * FACTORY, + * GObject) + * + * // Creates your own platform view factory. Messenger can be used for + * // Dart-side code to control single #FlPlatformView (or #GtkWidget). + * WebViewFactory * + * webview_factory_new(FlBinaryMessenger *messenger); + * ]| + * + * For source of your subclass: + * |[ + * // platform_view_factory.c + * // Type definitions and webview_factory_new are omitted. + * + * static FlPlatformView * + * webview_factory_create_platform_view (FlPlatformViewFactory *factory, + * int64_t view_identifier, + * FlValue *args) { + * // Creates a new instance of your custom #FlPlatformView implementation. + * // Flutter will take the reference, and release it when Dart-side widget + * // is destructed. + * WebViewFactory *self = WEBVIEW_FACTORY (factory); + * return FL_PLATFORM_VIEW ( + * webview_platform_view_new (self->messenger, view_identifier, args)); + * } + * + * static FlMessageCodec * + * webview_factory_get_create_arguments_codec (FlPlatformViewFactory *self) { + * // Simply creates a new instance of message codec. + * return FL_MESSAGE_CODEC (fl_standard_message_codec_new ()); + * } + * + * static void + * webview_factory_fl_platform_view_factory_iface_init ( + * FlPlatformViewFactoryInterface *iface) { + * iface->create_platform_view = webview_factory_create_platform_view; + * iface->get_create_arguments_codec = + * webview_factory_get_create_arguments_codec; + * } + * ]| + */ + +struct _FlPlatformViewFactoryInterface { + GTypeInterface parent_interface; + + /** + * FlPlatformViewFactory::create_platform_view: + * @factory: an #FlPlatformViewFactory. + * @view_identifier: a unique identifier for this #GtkWidget. + * @args: parameters for creating the #GtkWidget sent from the Dart side of + * the Flutter app. If get_create_arguments_codec is not implemented, + * or if no creation arguments were sent from the Dart code, this will + * be null. Otherwise this will be the value sent from the Dart code as + * decoded by get_create_arguments_codec. + * + * Creates an #FlPlatformView for embedding. + * + * Returns: (transfer full): an #FlPlatformView. + */ + FlPlatformView* (*create_platform_view)(FlPlatformViewFactory* factory, + int64_t view_identifier, + FlValue* args); + + /** + * FlPlatformViewFactory::get_create_arguments_codec: + * @factory: an #FlPlatformViewFactory. + * + * Creates an #FlMessageCodec if needed. Only needs to be implemented if + * create_platform_view needs arguments. + * + * Returns: (transfer full): an #FlMessageCodec for decoding the args + * parameter of create_platform_view, or %NULL if no creation arguments will + * be sent. + */ + FlMessageCodec* (*get_create_arguments_codec)(FlPlatformViewFactory* self); +}; + +/** + * fl_platform_view_factory_create_platform_view: + * @factory: an #FlPlatformViewFactory. + * @view_identifier: a unique identifier for this #GtkWidget. + * @args: parameters for creating the #GtkWidget sent from the Dart side of + * the Flutter app. If get_create_arguments_codec is not implemented, or + * if no creation arguments were sent from the Dart code, this will be + * null. Otherwise this will be the value sent from the Dart code as + * decoded by get_create_arguments_codec. + * + * Creates an #FlPlatformView for embedding. + * + * Returns: (transfer full): an #FlPlatformView. + */ +FlPlatformView* fl_platform_view_factory_create_platform_view( + FlPlatformViewFactory* factory, + int64_t view_identifier, + FlValue* args); + +/** + * fl_platform_view_factory_get_create_arguments_codec: + * @factory: an #FlPlatformViewFactory. + * + * Creates an #FlMessageCodec if needed. Only needs to be implemented if + * create_platform_view needs arguments. + * + * Returns: (transfer full): an #FlMessageCodec for decoding the args + * parameter of create_platform_view, or %NULL if no creation arguments will + * be sent. + */ +FlMessageCodec* fl_platform_view_factory_get_create_arguments_codec( + FlPlatformViewFactory* factory); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_PLATFORM_VIEWS_H_ diff --git a/shell/platform/linux/public/flutter_linux/fl_plugin_registrar.h b/shell/platform/linux/public/flutter_linux/fl_plugin_registrar.h index 5ac073c1844ae..bc7eb914b0102 100644 --- a/shell/platform/linux/public/flutter_linux/fl_plugin_registrar.h +++ b/shell/platform/linux/public/flutter_linux/fl_plugin_registrar.h @@ -12,6 +12,7 @@ #include #include "fl_binary_messenger.h" +#include "fl_platform_views.h" #include "fl_view.h" G_BEGIN_DECLS @@ -49,6 +50,22 @@ FlBinaryMessenger* fl_plugin_registrar_get_messenger( */ FlView* fl_plugin_registrar_get_view(FlPluginRegistrar* registrar); +/** + * fl_plugin_registrar_register_view_factory: + * @registrar: an #FlPluginRegistrar. + * @factory: (transfer none): the view factory that will be registered. + * @view_type: A unique identifier for the factory. The Dart code of the Flutter + * app can use this identifier to request creation of a #GtkWidget + * by the registered factory. + * + * Registers a #FlPlatformViewFactory for creation of platform views. + * Plugins can expose a GtkWidget for embedding in Flutter apps by registering a + * view factory. + */ +void fl_plugin_registrar_register_view_factory(FlPluginRegistrar* registrar, + FlPlatformViewFactory* factory, + const gchar* view_type); + G_END_DECLS #endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_PLUGIN_REGISTRAR_H_ diff --git a/shell/platform/linux/testing/mock_platform_views.cc b/shell/platform/linux/testing/mock_platform_views.cc new file mode 100644 index 0000000000000..cca8adde67530 --- /dev/null +++ b/shell/platform/linux/testing/mock_platform_views.cc @@ -0,0 +1,103 @@ +// 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/testing/mock_platform_views.h" + +struct _FlMockPlatformView { + FlPlatformView parent_instance; + + int64_t view_identifier; + + GtkWidget* (*get_view)(FlPlatformView* self); +}; + +G_DEFINE_TYPE(FlMockPlatformView, + fl_mock_platform_view, + fl_platform_view_get_type()) + +static GtkWidget* fl_mock_platform_view_get_view( + FlPlatformView* platform_view) { + g_return_val_if_fail(FL_IS_MOCK_PLATFORM_VIEW(platform_view), nullptr); + FlMockPlatformView* self = FL_MOCK_PLATFORM_VIEW(platform_view); + if (!self->get_view) + return nullptr; + else + return self->get_view(FL_PLATFORM_VIEW(self)); +} + +static void fl_mock_platform_view_class_init(FlMockPlatformViewClass* klass) { + FL_PLATFORM_VIEW_CLASS(klass)->get_view = fl_mock_platform_view_get_view; +} + +static void fl_mock_platform_view_init(FlMockPlatformView* self) {} + +FlMockPlatformView* fl_mock_platform_view_new( + GtkWidget* (*get_view)(FlPlatformView* self)) { + FlMockPlatformView* self = FL_MOCK_PLATFORM_VIEW( + g_object_new(fl_mock_platform_view_get_type(), nullptr)); + self->get_view = get_view; + return self; +} + +struct _FlMockViewFactory { + GObject parent_instance; + + FlPlatformView* (*create_platform_view)(FlPlatformViewFactory* self, + int64_t view_identifier, + FlValue* args); + FlMessageCodec* (*get_create_arguments_codec)(FlPlatformViewFactory* self); +}; + +static void fl_mock_view_factory_fl_platform_view_factory_iface_init( + FlPlatformViewFactoryInterface* iface); + +G_DEFINE_TYPE_WITH_CODE( + FlMockViewFactory, + fl_mock_view_factory, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE( + fl_platform_view_factory_get_type(), + fl_mock_view_factory_fl_platform_view_factory_iface_init)) + +static FlPlatformView* fl_mock_view_factory_create_platform_view( + FlPlatformViewFactory* factory, + int64_t view_identifier, + FlValue* args) { + FlMockViewFactory* self = FL_MOCK_VIEW_FACTORY(factory); + return self->create_platform_view(factory, view_identifier, args); +} + +static FlMessageCodec* fl_mock_view_factory_get_create_arguments_codec( + FlPlatformViewFactory* factory) { + FlMockViewFactory* self = FL_MOCK_VIEW_FACTORY(factory); + if (!self->get_create_arguments_codec) + return nullptr; + else + return self->get_create_arguments_codec(factory); +} + +static void fl_mock_view_factory_fl_platform_view_factory_iface_init( + FlPlatformViewFactoryInterface* iface) { + iface->create_platform_view = fl_mock_view_factory_create_platform_view; + iface->get_create_arguments_codec = + fl_mock_view_factory_get_create_arguments_codec; +} + +static void fl_mock_view_factory_class_init(FlMockViewFactoryClass* klass) {} + +static void fl_mock_view_factory_init(FlMockViewFactory* self) {} + +// Creates a mock platform_view_factory +FlMockViewFactory* fl_mock_view_factory_new( + FlPlatformView* (*create_platform_view)(FlPlatformViewFactory* self, + int64_t view_identifier, + FlValue* args), + FlMessageCodec* (*get_create_arguments_codec)( + FlPlatformViewFactory* self)) { + FlMockViewFactory* self = FL_MOCK_VIEW_FACTORY( + g_object_new(fl_mock_view_factory_get_type(), nullptr)); + self->create_platform_view = create_platform_view; + self->get_create_arguments_codec = get_create_arguments_codec; + return self; +} diff --git a/shell/platform/linux/testing/mock_platform_views.h b/shell/platform/linux/testing/mock_platform_views.h new file mode 100644 index 0000000000000..5e919f10a39ec --- /dev/null +++ b/shell/platform/linux/testing/mock_platform_views.h @@ -0,0 +1,33 @@ +// 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/public/flutter_linux/fl_platform_views.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FlMockPlatformView, + fl_mock_platform_view, + FL, + MOCK_PLATFORM_VIEW, + FlPlatformView) + +FlMockPlatformView* fl_mock_platform_view_new( + GtkWidget* (*get_view)(FlPlatformView* self)); + +int64_t fl_mock_platform_view_get_view_identifier( + FlMockPlatformView* platform_view); + +G_DECLARE_FINAL_TYPE(FlMockViewFactory, + fl_mock_view_factory, + FL, + MOCK_VIEW_FACTORY, + GObject) + +FlMockViewFactory* fl_mock_view_factory_new( + FlPlatformView* (*create_platform_view)(FlPlatformViewFactory* self, + int64_t view_identifier, + FlValue* args), + FlMessageCodec* (*get_create_arguments_codec)(FlPlatformViewFactory* self)); + +G_END_DECLS