10 #include <gtk/gtk-a11y.h>
80 FlPluginRegistryInterface* iface);
83 FlKeyboardViewDelegateInterface* iface);
86 FlScrollingViewDelegateInterface* iface);
89 FlTextInputViewDelegateInterface* iface);
95 G_IMPLEMENT_INTERFACE(fl_plugin_registry_get_type(),
97 G_IMPLEMENT_INTERFACE(fl_keyboard_view_delegate_get_type(),
99 G_IMPLEMENT_INTERFACE(fl_scrolling_view_delegate_get_type(),
101 G_IMPLEMENT_INTERFACE(fl_text_input_view_delegate_get_type(),
105 static gboolean window_delete_event_cb(FlView*
self) {
116 gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(
self)));
117 g_return_if_fail(GDK_IS_WINDOW(window));
118 g_autoptr(GtkIMContext) im_context = gtk_im_multicontext_new();
119 gtk_im_context_set_client_window(im_context, window);
121 g_clear_object(&self->text_input_plugin);
123 messenger, im_context, FL_TEXT_INPUT_VIEW_DELEGATE(
self));
124 g_clear_object(&self->keyboard_manager);
125 self->keyboard_manager =
130 g_clear_object(&self->scrolling_manager);
131 self->scrolling_manager =
136 GdkDevice* device = gdk_event_get_source_device(
event);
137 GdkInputSource source = gdk_device_get_source(device);
140 case GDK_SOURCE_ERASER:
141 case GDK_SOURCE_CURSOR:
142 case GDK_SOURCE_TABLET_PAD:
143 return kFlutterPointerDeviceKindStylus;
144 case GDK_SOURCE_TOUCHSCREEN:
145 return kFlutterPointerDeviceKindTouch;
146 case GDK_SOURCE_TOUCHPAD:
147 case GDK_SOURCE_TRACKPOINT:
148 case GDK_SOURCE_KEYBOARD:
149 case GDK_SOURCE_MOUSE:
150 return kFlutterPointerDeviceKindMouse;
156 guint event_time = gdk_event_get_time(
event);
157 GdkEventType event_type = gdk_event_get_event_type(
event);
158 GdkModifierType event_state =
static_cast<GdkModifierType
>(0);
159 gdk_event_get_state(
event, &event_state);
160 guint event_button = 0;
161 gdk_event_get_button(
event, &event_button);
162 gdouble event_x = 0.0, event_y = 0.0;
163 gdk_event_get_coords(
event, &event_x, &event_y);
166 switch (event_button) {
168 button = kFlutterPointerButtonMousePrimary;
171 button = kFlutterPointerButtonMouseMiddle;
174 button = kFlutterPointerButtonMouseSecondary;
179 int old_button_state =
self->button_state;
180 FlutterPointerPhase phase = kMove;
181 if (event_type == GDK_BUTTON_PRESS) {
183 if ((self->button_state & button) != 0) {
186 self->button_state ^= button;
188 phase = old_button_state == 0 ? kDown : kMove;
189 }
else if (event_type == GDK_BUTTON_RELEASE) {
191 if ((self->button_state & button) == 0) {
194 self->button_state ^= button;
196 phase =
self->button_state == 0 ? kUp : kMove;
199 if (self->engine ==
nullptr) {
203 gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(
self));
205 self->scrolling_manager, event_x * scale_factor, event_y * scale_factor);
207 event_state, event_time);
210 event_x * scale_factor, event_y * scale_factor,
218 if (!self->pointer_inside) {
219 self->pointer_inside =
TRUE;
222 if (gdk_event_get_coords(
event, &x, &y)) {
223 gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(
self));
236 GtkAllocation allocation;
237 gtk_widget_get_allocation(GTK_WIDGET(
self), &allocation);
238 gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(
self));
240 self->engine, allocation.width * scale_factor,
241 allocation.height * scale_factor, scale_factor);
249 if (allocation.width > 1 && allocation.height > 1 &&
250 gtk_widget_get_realized(GTK_WIDGET(
self))) {
252 allocation.width * scale_factor,
253 allocation.height * scale_factor);
259 const FlutterSemanticsUpdate2* update,
280 FlPluginRegistry* registry,
282 FlView*
self = FL_VIEW(registry);
290 FlPluginRegistryInterface* iface) {
295 FlKeyboardViewDelegateInterface* iface) {
296 iface->send_key_event =
297 [](FlKeyboardViewDelegate* view_delegate,
const FlutterKeyEvent*
event,
299 FlView*
self = FL_VIEW(view_delegate);
300 if (self->engine !=
nullptr) {
305 iface->text_filter_key_press = [](FlKeyboardViewDelegate* view_delegate,
307 FlView*
self = FL_VIEW(view_delegate);
311 iface->get_messenger = [](FlKeyboardViewDelegate* view_delegate) {
312 FlView*
self = FL_VIEW(view_delegate);
316 iface->redispatch_event = [](FlKeyboardViewDelegate* view_delegate,
317 std::unique_ptr<FlKeyEvent> in_event) {
319 GdkEventType event_type = gdk_event_get_event_type(
event->
origin);
320 g_return_if_fail(event_type == GDK_KEY_PRESS ||
321 event_type == GDK_KEY_RELEASE);
326 iface->subscribe_to_layout_change = [](FlKeyboardViewDelegate* view_delegate,
328 FlView*
self = FL_VIEW(view_delegate);
329 self->keyboard_layout_notifier = std::move(notifier);
332 iface->lookup_key = [](FlKeyboardViewDelegate* view_delegate,
333 const GdkKeymapKey* key) -> guint {
334 FlView*
self = FL_VIEW(view_delegate);
335 g_return_val_if_fail(self->keymap !=
nullptr, 0);
336 return gdk_keymap_lookup_key(self->keymap, key);
339 iface->get_keyboard_state =
340 [](FlKeyboardViewDelegate* view_delegate) -> GHashTable* {
341 FlView*
self = FL_VIEW(view_delegate);
347 FlScrollingViewDelegateInterface* iface) {
348 iface->send_mouse_pointer_event =
349 [](FlScrollingViewDelegate* view_delegate, FlutterPointerPhase phase,
350 size_t timestamp,
double x,
double y,
351 FlutterPointerDeviceKind device_kind,
double scroll_delta_x,
352 double scroll_delta_y, int64_t buttons) {
353 FlView*
self = FL_VIEW(view_delegate);
354 if (self->engine !=
nullptr) {
356 y, device_kind, scroll_delta_x,
357 scroll_delta_y, buttons);
360 iface->send_pointer_pan_zoom_event =
361 [](FlScrollingViewDelegate* view_delegate,
size_t timestamp,
double x,
362 double y, FlutterPointerPhase phase,
double pan_x,
double pan_y,
363 double scale,
double rotation) {
364 FlView*
self = FL_VIEW(view_delegate);
365 if (self->engine !=
nullptr) {
367 phase, pan_x, pan_y, scale,
374 FlTextInputViewDelegateInterface* iface) {
375 iface->translate_coordinates = [](FlTextInputViewDelegate* delegate,
376 gint view_x, gint view_y, gint* window_x,
378 FlView*
self = FL_VIEW(delegate);
379 gtk_widget_translate_coordinates(GTK_WIDGET(
self),
380 gtk_widget_get_toplevel(GTK_WIDGET(
self)),
381 view_x, view_y, window_x, window_y);
387 GdkEventButton* button_event) {
388 GdkEvent*
event =
reinterpret_cast<GdkEvent*
>(button_event);
391 GdkEventType event_type = gdk_event_get_event_type(
event);
392 if (event_type == GDK_DOUBLE_BUTTON_PRESS ||
393 event_type == GDK_TRIPLE_BUTTON_PRESS) {
404 GdkEventButton* button_event) {
405 GdkEvent*
event =
reinterpret_cast<GdkEvent*
>(button_event);
415 self->scrolling_manager,
event,
416 gtk_widget_get_scale_factor(GTK_WIDGET(
self)));
422 GdkEventMotion* motion_event) {
423 GdkEvent*
event =
reinterpret_cast<GdkEvent*
>(motion_event);
425 if (self->engine ==
nullptr) {
429 guint event_time = gdk_event_get_time(
event);
430 GdkModifierType event_state =
static_cast<GdkModifierType
>(0);
431 gdk_event_get_state(
event, &event_state);
432 gdouble event_x = 0.0, event_y = 0.0;
433 gdk_event_get_coords(
event, &event_x, &event_y);
437 gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(
self));
440 event_state, event_time);
442 self->engine, self->button_state != 0 ? kMove : kHover,
452 GdkEventCrossing* crossing_event) {
453 GdkEvent*
event =
reinterpret_cast<GdkEvent*
>(crossing_event);
455 if (self->engine ==
nullptr) {
466 GdkEventCrossing* crossing_event) {
467 GdkEvent*
event =
reinterpret_cast<GdkEvent*
>(crossing_event);
469 guint event_time = gdk_event_get_time(
event);
470 gdouble event_x = 0.0, event_y = 0.0;
471 gdk_event_get_coords(
event, &event_x, &event_y);
473 if (crossing_event->mode != GDK_CROSSING_NORMAL) {
477 if (self->engine ==
nullptr) {
484 if (self->pointer_inside && self->button_state == 0) {
485 gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(
self));
488 event_x * scale_factor, event_y * scale_factor,
490 self->pointer_inside = FALSE;
497 if (self->keyboard_layout_notifier ==
nullptr) {
501 self->keyboard_layout_notifier();
532 g_return_val_if_fail(FL_IS_ENGINE(self->engine), FALSE);
534 GdkWindowState
state =
event->window_state.new_window_state;
535 GdkWindowState previous_state =
self->window_state;
536 self->window_state =
state;
537 bool was_visible = !((previous_state & GDK_WINDOW_STATE_WITHDRAWN) ||
538 (previous_state & GDK_WINDOW_STATE_ICONIFIED));
539 bool is_visible = !((
state & GDK_WINDOW_STATE_WITHDRAWN) ||
540 (
state & GDK_WINDOW_STATE_ICONIFIED));
541 bool was_focused = (previous_state & GDK_WINDOW_STATE_FOCUSED);
542 bool is_focused = (
state & GDK_WINDOW_STATE_FOCUSED);
543 if (was_visible != is_visible || was_focused != is_focused) {
544 if (self->engine !=
nullptr) {
554 gtk_widget_get_parent_window(GTK_WIDGET(
self)));
557 self->keymap = gdk_keymap_get_for_display(gdk_display_get_default());
558 self->keymap_keys_changed_cb_id = g_signal_connect_swapped(
567 g_autoptr(GError)
error =
nullptr;
569 gtk_gl_area_set_error(self->gl_area,
error);
573 return GDK_GL_CONTEXT(
578 g_autoptr(GError)
error =
nullptr;
582 GError* gl_error = gtk_gl_area_get_error(self->gl_area);
583 if (gl_error != NULL) {
584 g_warning(
"Failed to initialize GLArea: %s", gl_error->message);
591 GtkWidget* toplevel_window = gtk_widget_get_toplevel(GTK_WIDGET(
self));
594 self->window_state_cb_id =
595 g_signal_connect_swapped(toplevel_window,
"window-state-event",
598 gdk_window_get_state(gtk_widget_get_window(toplevel_window));
600 g_signal_connect_swapped(toplevel_window,
"delete-event",
601 G_CALLBACK(window_delete_event_cb),
self);
608 g_warning(
"Failed to start Flutter engine: %s",
error->message);
616 FL_SOCKET_ACCESSIBLE(gtk_widget_get_accessible(GTK_WIDGET(
self))),
617 atk_plug_get_id(ATK_PLUG(self->view_accessible)));
620 static gboolean
render_cb(FlView*
self, GdkGLContext* context) {
621 if (gtk_gl_area_get_error(self->gl_area) != NULL) {
625 int width = gtk_widget_get_allocated_width(GTK_WIDGET(self->gl_area));
626 int height = gtk_widget_get_allocated_height(GTK_WIDGET(self->gl_area));
627 gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self->gl_area));
635 g_autoptr(GError)
error =
nullptr;
639 GError* gl_error = gtk_gl_area_get_error(self->gl_area);
640 if (gl_error != NULL) {
641 g_warning(
"Failed to uninitialize GLArea: %s", gl_error->message);
653 FlView*
self = FL_VIEW(
object);
656 self->engine =
fl_engine_new(self->project, FL_RENDERER(self->renderer));
667 FlView*
self = FL_VIEW(
object);
671 g_set_object(&self->project,
672 static_cast<FlDartProject*
>(g_value_get_object(
value)));
675 G_OBJECT_WARN_INVALID_PROPERTY_ID(
object,
prop_id,
pspec);
684 FlView*
self = FL_VIEW(
object);
688 g_value_set_object(
value, self->project);
691 G_OBJECT_WARN_INVALID_PROPERTY_ID(
object,
prop_id,
pspec);
697 FlView*
self = FL_VIEW(
object);
699 if (strcmp(
pspec->name,
"scale-factor") == 0) {
703 if (G_OBJECT_CLASS(fl_view_parent_class)->notify !=
nullptr) {
704 G_OBJECT_CLASS(fl_view_parent_class)->notify(
object,
pspec);
709 FlView*
self = FL_VIEW(
object);
711 if (self->engine !=
nullptr) {
718 if (self->window_state_cb_id != 0) {
719 GtkWidget* toplevel_window = gtk_widget_get_toplevel(GTK_WIDGET(
self));
720 g_signal_handler_disconnect(toplevel_window, self->window_state_cb_id);
721 self->window_state_cb_id = 0;
724 g_clear_object(&self->project);
725 g_clear_object(&self->renderer);
726 g_clear_object(&self->engine);
727 g_clear_object(&self->keyboard_manager);
728 if (self->keymap_keys_changed_cb_id != 0) {
729 g_signal_handler_disconnect(self->keymap, self->keymap_keys_changed_cb_id);
730 self->keymap_keys_changed_cb_id = 0;
732 g_clear_object(&self->mouse_cursor_plugin);
733 g_clear_object(&self->platform_plugin);
734 g_clear_object(&self->view_accessible);
736 G_OBJECT_CLASS(fl_view_parent_class)->dispose(
object);
741 FlView*
self = FL_VIEW(widget);
745 reinterpret_cast<GdkEvent*
>(
event))));
750 GdkEventKey*
event) {
751 FlView*
self = FL_VIEW(widget);
754 reinterpret_cast<GdkEvent*
>(
event))));
758 GObjectClass* object_class = G_OBJECT_CLASS(klass);
765 GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
769 g_object_class_install_property(
772 "flutter-project",
"flutter-project",
"Flutter project in use",
773 fl_dart_project_get_type(),
774 static_cast<GParamFlags
>(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
775 G_PARAM_STATIC_STRINGS)));
777 gtk_widget_class_set_accessible_type(GTK_WIDGET_CLASS(klass),
778 fl_socket_accessible_get_type());
782 gtk_widget_set_can_focus(GTK_WIDGET(
self),
TRUE);
784 self->event_box = gtk_event_box_new();
785 gtk_widget_set_hexpand(self->event_box,
TRUE);
786 gtk_widget_set_vexpand(self->event_box,
TRUE);
787 gtk_container_add(GTK_CONTAINER(
self), self->event_box);
788 gtk_widget_show(self->event_box);
789 gtk_widget_add_events(self->event_box,
790 GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK |
791 GDK_BUTTON_RELEASE_MASK | GDK_SCROLL_MASK |
792 GDK_SMOOTH_SCROLL_MASK);
794 g_signal_connect_swapped(self->event_box,
"button-press-event",
796 g_signal_connect_swapped(self->event_box,
"button-release-event",
798 g_signal_connect_swapped(self->event_box,
"scroll-event",
800 g_signal_connect_swapped(self->event_box,
"motion-notify-event",
802 g_signal_connect_swapped(self->event_box,
"enter-notify-event",
804 g_signal_connect_swapped(self->event_box,
"leave-notify-event",
806 GtkGesture* zoom = gtk_gesture_zoom_new(self->event_box);
809 g_signal_connect_swapped(zoom,
"scale-changed",
812 GtkGesture* rotate = gtk_gesture_rotate_new(self->event_box);
813 g_signal_connect_swapped(rotate,
"begin",
815 g_signal_connect_swapped(rotate,
"angle-changed",
820 self->gl_area = GTK_GL_AREA(gtk_gl_area_new());
821 gtk_widget_show(GTK_WIDGET(self->gl_area));
822 gtk_container_add(GTK_CONTAINER(self->event_box), GTK_WIDGET(self->gl_area));
824 g_signal_connect_swapped(self->gl_area,
"create-context",
826 g_signal_connect_swapped(self->gl_area,
"realize", G_CALLBACK(
realize_cb),
828 g_signal_connect_swapped(self->gl_area,
"render", G_CALLBACK(
render_cb),
830 g_signal_connect_swapped(self->gl_area,
"unrealize", G_CALLBACK(
unrealize_cb),
833 g_signal_connect_swapped(
self,
"size-allocate", G_CALLBACK(
size_allocate_cb),
838 return static_cast<FlView*
>(
839 g_object_new(fl_view_get_type(),
"flutter-project", project,
nullptr));
843 g_return_val_if_fail(FL_IS_VIEW(
self),
nullptr);
848 g_return_if_fail(FL_IS_VIEW(
self));
849 gtk_widget_queue_draw(GTK_WIDGET(self->gl_area));
853 g_return_val_if_fail(FL_IS_VIEW(
self),
nullptr);