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) {
555 self->engine =
fl_engine_new(self->project, FL_RENDERER(self->renderer));
562 self->keymap = gdk_keymap_get_for_display(gdk_display_get_default());
563 self->keymap_keys_changed_cb_id = g_signal_connect_swapped(
572 g_autoptr(GError)
error =
nullptr;
574 gtk_gl_area_set_error(self->gl_area,
error);
578 return GDK_GL_CONTEXT(
583 g_autoptr(GError)
error =
nullptr;
587 GError* gl_error = gtk_gl_area_get_error(self->gl_area);
588 if (gl_error != NULL) {
589 g_warning(
"Failed to initialize GLArea: %s", gl_error->message);
596 GtkWidget* toplevel_window = gtk_widget_get_toplevel(GTK_WIDGET(
self));
599 self->window_state_cb_id =
600 g_signal_connect_swapped(toplevel_window,
"window-state-event",
603 gdk_window_get_state(gtk_widget_get_window(toplevel_window));
605 g_signal_connect_swapped(toplevel_window,
"delete-event",
606 G_CALLBACK(window_delete_event_cb),
self);
613 g_warning(
"Failed to start Flutter engine: %s",
error->message);
621 FL_SOCKET_ACCESSIBLE(gtk_widget_get_accessible(GTK_WIDGET(
self))),
622 atk_plug_get_id(ATK_PLUG(self->view_accessible)));
625 static gboolean
render_cb(FlView*
self, GdkGLContext* context) {
626 if (gtk_gl_area_get_error(self->gl_area) != NULL) {
630 int width = gtk_widget_get_allocated_width(GTK_WIDGET(self->gl_area));
631 int height = gtk_widget_get_allocated_height(GTK_WIDGET(self->gl_area));
632 gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self->gl_area));
640 g_autoptr(GError)
error =
nullptr;
644 GError* gl_error = gtk_gl_area_get_error(self->gl_area);
645 if (gl_error != NULL) {
646 g_warning(
"Failed to uninitialize GLArea: %s", gl_error->message);
661 FlView*
self = FL_VIEW(
object);
665 g_set_object(&self->project,
666 static_cast<FlDartProject*
>(g_value_get_object(
value)));
669 G_OBJECT_WARN_INVALID_PROPERTY_ID(
object,
prop_id,
pspec);
678 FlView*
self = FL_VIEW(
object);
682 g_value_set_object(
value, self->project);
685 G_OBJECT_WARN_INVALID_PROPERTY_ID(
object,
prop_id,
pspec);
691 FlView*
self = FL_VIEW(
object);
693 if (strcmp(
pspec->name,
"scale-factor") == 0) {
697 if (G_OBJECT_CLASS(fl_view_parent_class)->notify !=
nullptr) {
698 G_OBJECT_CLASS(fl_view_parent_class)->notify(
object,
pspec);
703 FlView*
self = FL_VIEW(
object);
705 if (self->engine !=
nullptr) {
712 if (self->window_state_cb_id != 0) {
713 GtkWidget* toplevel_window = gtk_widget_get_toplevel(GTK_WIDGET(
self));
714 g_signal_handler_disconnect(toplevel_window, self->window_state_cb_id);
715 self->window_state_cb_id = 0;
718 g_clear_object(&self->project);
719 g_clear_object(&self->renderer);
720 g_clear_object(&self->engine);
721 g_clear_object(&self->keyboard_manager);
722 if (self->keymap_keys_changed_cb_id != 0) {
723 g_signal_handler_disconnect(self->keymap, self->keymap_keys_changed_cb_id);
724 self->keymap_keys_changed_cb_id = 0;
726 g_clear_object(&self->mouse_cursor_plugin);
727 g_clear_object(&self->platform_plugin);
728 g_clear_object(&self->view_accessible);
730 G_OBJECT_CLASS(fl_view_parent_class)->dispose(
object);
735 FlView*
self = FL_VIEW(widget);
739 reinterpret_cast<GdkEvent*
>(
event))));
744 GdkEventKey*
event) {
745 FlView*
self = FL_VIEW(widget);
748 reinterpret_cast<GdkEvent*
>(
event))));
752 GObjectClass* object_class = G_OBJECT_CLASS(klass);
758 GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
762 g_object_class_install_property(
765 "flutter-project",
"flutter-project",
"Flutter project in use",
766 fl_dart_project_get_type(),
767 static_cast<GParamFlags
>(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
768 G_PARAM_STATIC_STRINGS)));
770 gtk_widget_class_set_accessible_type(GTK_WIDGET_CLASS(klass),
771 fl_socket_accessible_get_type());
775 gtk_widget_set_can_focus(GTK_WIDGET(
self),
TRUE);
777 self->event_box = gtk_event_box_new();
778 gtk_widget_set_hexpand(self->event_box,
TRUE);
779 gtk_widget_set_vexpand(self->event_box,
TRUE);
780 gtk_container_add(GTK_CONTAINER(
self), self->event_box);
781 gtk_widget_show(self->event_box);
782 gtk_widget_add_events(self->event_box,
783 GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK |
784 GDK_BUTTON_RELEASE_MASK | GDK_SCROLL_MASK |
785 GDK_SMOOTH_SCROLL_MASK);
787 g_signal_connect_swapped(self->event_box,
"button-press-event",
789 g_signal_connect_swapped(self->event_box,
"button-release-event",
791 g_signal_connect_swapped(self->event_box,
"scroll-event",
793 g_signal_connect_swapped(self->event_box,
"motion-notify-event",
795 g_signal_connect_swapped(self->event_box,
"enter-notify-event",
797 g_signal_connect_swapped(self->event_box,
"leave-notify-event",
799 GtkGesture* zoom = gtk_gesture_zoom_new(self->event_box);
802 g_signal_connect_swapped(zoom,
"scale-changed",
805 GtkGesture* rotate = gtk_gesture_rotate_new(self->event_box);
806 g_signal_connect_swapped(rotate,
"begin",
808 g_signal_connect_swapped(rotate,
"angle-changed",
813 self->gl_area = GTK_GL_AREA(gtk_gl_area_new());
814 gtk_widget_show(GTK_WIDGET(self->gl_area));
815 gtk_container_add(GTK_CONTAINER(self->event_box), GTK_WIDGET(self->gl_area));
817 g_signal_connect_swapped(self->gl_area,
"create-context",
819 g_signal_connect_swapped(self->gl_area,
"realize", G_CALLBACK(
realize_cb),
821 g_signal_connect_swapped(self->gl_area,
"render", G_CALLBACK(
render_cb),
823 g_signal_connect_swapped(self->gl_area,
"unrealize", G_CALLBACK(
unrealize_cb),
826 g_signal_connect_swapped(
self,
"size-allocate", G_CALLBACK(
size_allocate_cb),
831 return static_cast<FlView*
>(
832 g_object_new(fl_view_get_type(),
"flutter-project", project,
nullptr));
836 g_return_val_if_fail(FL_IS_VIEW(
self),
nullptr);
841 g_return_if_fail(FL_IS_VIEW(
self));
842 gtk_widget_queue_draw(GTK_WIDGET(self->gl_area));
846 g_return_val_if_fail(FL_IS_VIEW(
self),
nullptr);