mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Delay the window until the first frame is received from the Flutter engine (flutter/engine#54703)
Fixes https://github.com/flutter/flutter/issues/151098
This commit is contained in:
parent
f64f4e4188
commit
3443af6a5c
@ -31,6 +31,16 @@ G_DEFINE_TYPE_WITH_CODE(FlApplication,
|
||||
GTK_TYPE_APPLICATION,
|
||||
G_ADD_PRIVATE(FlApplication))
|
||||
|
||||
// Called when the first frame is received.
|
||||
static void first_frame_cb(FlApplication* self, FlView* view) {
|
||||
GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(view));
|
||||
|
||||
// Show the main window.
|
||||
if (window != nullptr && GTK_IS_WINDOW(window)) {
|
||||
gtk_window_present(GTK_WINDOW(window));
|
||||
}
|
||||
}
|
||||
|
||||
// Default implementation of FlApplication::register_plugins
|
||||
static void fl_application_register_plugins(FlApplication* self,
|
||||
FlPluginRegistry* registry) {}
|
||||
@ -80,17 +90,20 @@ static void fl_application_activate(GApplication* application) {
|
||||
project, priv->dart_entrypoint_arguments);
|
||||
|
||||
FlView* view = fl_view_new(project);
|
||||
g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb),
|
||||
self);
|
||||
gtk_widget_show(GTK_WIDGET(view));
|
||||
|
||||
GtkWindow* window;
|
||||
g_signal_emit(self, fl_application_signals[kSignalCreateWindow], 0, view,
|
||||
&window);
|
||||
gtk_widget_show(GTK_WIDGET(window));
|
||||
|
||||
// Make the resources for the view so rendering can start.
|
||||
// We'll show the view when we have the first frame.
|
||||
gtk_widget_realize(GTK_WIDGET(view));
|
||||
|
||||
g_signal_emit(self, fl_application_signals[kSignalRegisterPlugins], 0,
|
||||
FL_PLUGIN_REGISTRY(view));
|
||||
|
||||
gtk_widget_grab_focus(GTK_WIDGET(view));
|
||||
}
|
||||
|
||||
// Implements GApplication::local_command_line.
|
||||
|
||||
@ -52,6 +52,9 @@ struct _FlView {
|
||||
// Background color.
|
||||
GdkRGBA* background_color;
|
||||
|
||||
// TRUE if have got the first frame to render.
|
||||
gboolean have_first_frame;
|
||||
|
||||
// Pointer button state recorded for sending status updates.
|
||||
int64_t button_state;
|
||||
|
||||
@ -82,7 +85,9 @@ struct _FlView {
|
||||
FlViewAccessible* view_accessible;
|
||||
};
|
||||
|
||||
enum { kPropFlutterProject = 1, kPropLast };
|
||||
enum { kSignalFirstFrame, kSignalLastSignal };
|
||||
|
||||
static guint fl_view_signals[kSignalLastSignal];
|
||||
|
||||
static void fl_view_plugin_registry_iface_init(
|
||||
FlPluginRegistryInterface* iface);
|
||||
@ -109,6 +114,15 @@ G_DEFINE_TYPE_WITH_CODE(
|
||||
G_IMPLEMENT_INTERFACE(fl_text_input_view_delegate_get_type(),
|
||||
fl_view_text_input_delegate_iface_init))
|
||||
|
||||
// Emit the first frame signal in the main thread.
|
||||
static gboolean first_frame_idle_cb(gpointer user_data) {
|
||||
FlView* self = FL_VIEW(user_data);
|
||||
|
||||
g_signal_emit(self, fl_view_signals[kSignalFirstFrame], 0);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Signal handler for GtkWidget::delete-event
|
||||
static gboolean window_delete_event_cb(FlView* self) {
|
||||
fl_platform_handler_request_app_exit(self->platform_handler);
|
||||
@ -678,6 +692,16 @@ static void fl_view_dispose(GObject* object) {
|
||||
G_OBJECT_CLASS(fl_view_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
// Implements GtkWidget::realize.
|
||||
static void fl_view_realize(GtkWidget* widget) {
|
||||
FlView* self = FL_VIEW(widget);
|
||||
|
||||
GTK_WIDGET_CLASS(fl_view_parent_class)->realize(widget);
|
||||
|
||||
// Realize the child widgets.
|
||||
gtk_widget_realize(GTK_WIDGET(self->gl_area));
|
||||
}
|
||||
|
||||
// Implements GtkWidget::key_press_event.
|
||||
static gboolean fl_view_key_press_event(GtkWidget* widget, GdkEventKey* event) {
|
||||
FlView* self = FL_VIEW(widget);
|
||||
@ -702,9 +726,14 @@ static void fl_view_class_init(FlViewClass* klass) {
|
||||
object_class->dispose = fl_view_dispose;
|
||||
|
||||
GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
|
||||
widget_class->realize = fl_view_realize;
|
||||
widget_class->key_press_event = fl_view_key_press_event;
|
||||
widget_class->key_release_event = fl_view_key_release_event;
|
||||
|
||||
fl_view_signals[kSignalFirstFrame] =
|
||||
g_signal_new("first-frame", fl_view_get_type(), G_SIGNAL_RUN_LAST, 0,
|
||||
NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||||
|
||||
gtk_widget_class_set_accessible_type(GTK_WIDGET_CLASS(klass),
|
||||
fl_socket_accessible_get_type());
|
||||
}
|
||||
@ -810,7 +839,15 @@ G_MODULE_EXPORT void fl_view_set_background_color(FlView* self,
|
||||
|
||||
void fl_view_redraw(FlView* self) {
|
||||
g_return_if_fail(FL_IS_VIEW(self));
|
||||
|
||||
gtk_widget_queue_draw(GTK_WIDGET(self->gl_area));
|
||||
|
||||
if (!self->have_first_frame) {
|
||||
self->have_first_frame = TRUE;
|
||||
// This is not the main thread, so the signal needs to be done via an idle
|
||||
// callback.
|
||||
g_idle_add(first_frame_idle_cb, self);
|
||||
}
|
||||
}
|
||||
|
||||
GHashTable* fl_view_get_keyboard_state(FlView* self) {
|
||||
|
||||
@ -3,31 +3,57 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h"
|
||||
#include "flutter/shell/platform/linux/fl_view_private.h"
|
||||
#include "flutter/shell/platform/linux/testing/fl_test_gtk_logs.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
static void first_frame_cb(FlView* view, gboolean* first_frame_emitted) {
|
||||
*first_frame_emitted = TRUE;
|
||||
}
|
||||
|
||||
TEST(FlViewTest, GetEngine) {
|
||||
flutter::testing::fl_ensure_gtk_init();
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
g_autoptr(FlView) view = fl_view_new(project);
|
||||
FlView* view = fl_view_new(project);
|
||||
|
||||
// Check the engine is immediately available (i.e. before the widget is
|
||||
// realized).
|
||||
FlEngine* engine = fl_view_get_engine(view);
|
||||
EXPECT_NE(engine, nullptr);
|
||||
|
||||
g_object_ref_sink(view);
|
||||
}
|
||||
|
||||
TEST(FlViewTest, StateUpdateDoesNotHappenInInit) {
|
||||
flutter::testing::fl_ensure_gtk_init();
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
g_autoptr(FlView) view = fl_view_new(project);
|
||||
FlView* view = fl_view_new(project);
|
||||
// Check that creating a view doesn't try to query the window state in
|
||||
// initialization, causing a critical log to be issued.
|
||||
EXPECT_EQ(
|
||||
flutter::testing::fl_get_received_gtk_log_levels() & G_LOG_LEVEL_CRITICAL,
|
||||
(GLogLevelFlags)0x0);
|
||||
g_object_ref_sink(view);
|
||||
|
||||
(void)view;
|
||||
}
|
||||
|
||||
TEST(FlViewTest, FirstFrameSignal) {
|
||||
flutter::testing::fl_ensure_gtk_init();
|
||||
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
FlView* view = fl_view_new(project);
|
||||
gboolean first_frame_emitted = FALSE;
|
||||
g_signal_connect(view, "first-frame", G_CALLBACK(first_frame_cb),
|
||||
&first_frame_emitted);
|
||||
|
||||
EXPECT_FALSE(first_frame_emitted);
|
||||
|
||||
fl_view_redraw(view);
|
||||
|
||||
// Signal is emitted in idle, clear the main loop.
|
||||
while (g_main_context_iteration(g_main_context_default(), FALSE)) {
|
||||
// Repeat until nothing to iterate on.
|
||||
}
|
||||
|
||||
// Check view has detected frame.
|
||||
EXPECT_TRUE(first_frame_emitted);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user