mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Add fl_engine_add/remove_view (flutter/engine#54018)
This is API that will be required when we get multi-view support on Linux. While this internal API is not currently used, I've made a PR so it can be more easily reviewed.
This commit is contained in:
parent
d11f10ccb6
commit
e1f2360083
@ -49,6 +49,9 @@ struct _FlEngine {
|
||||
FLUTTER_API_SYMBOL(FlutterEngine) engine;
|
||||
FlutterEngineProcTable embedder_api;
|
||||
|
||||
// Next ID to use for a view.
|
||||
FlutterViewId next_view_id;
|
||||
|
||||
// Function to call when a platform message is received.
|
||||
FlEnginePlatformMessageHandler platform_message_handler;
|
||||
gpointer platform_message_handler_data;
|
||||
@ -123,6 +126,29 @@ static void parse_locale(const gchar* locale,
|
||||
}
|
||||
}
|
||||
|
||||
static void view_added_cb(const FlutterAddViewResult* result) {
|
||||
g_autoptr(GTask) task = G_TASK(result->user_data);
|
||||
|
||||
FlutterViewId view_id = GPOINTER_TO_INT(g_task_get_task_data(task));
|
||||
if (result->added) {
|
||||
g_task_return_pointer(task, GINT_TO_POINTER(view_id), nullptr);
|
||||
} else {
|
||||
g_task_return_new_error(task, fl_engine_error_quark(),
|
||||
FL_ENGINE_ERROR_FAILED, "Failed to add view");
|
||||
}
|
||||
}
|
||||
|
||||
static void view_removed_cb(const FlutterRemoveViewResult* result) {
|
||||
g_autoptr(GTask) task = G_TASK(result->user_data);
|
||||
|
||||
if (result->removed) {
|
||||
g_task_return_boolean(task, TRUE);
|
||||
} else {
|
||||
g_task_return_new_error(task, fl_engine_error_quark(),
|
||||
FL_ENGINE_ERROR_FAILED, "Failed to remove view");
|
||||
}
|
||||
}
|
||||
|
||||
// Passes locale information to the Flutter engine.
|
||||
static void setup_locales(FlEngine* self) {
|
||||
const gchar* const* languages = g_get_language_names();
|
||||
@ -177,12 +203,11 @@ static bool compositor_collect_backing_store_callback(
|
||||
}
|
||||
|
||||
// Called when embedder should composite contents of each layer onto the screen.
|
||||
static bool compositor_present_layers_callback(const FlutterLayer** layers,
|
||||
size_t layers_count,
|
||||
void* user_data) {
|
||||
g_return_val_if_fail(FL_IS_RENDERER(user_data), false);
|
||||
return fl_renderer_present_layers(FL_RENDERER(user_data), layers,
|
||||
layers_count);
|
||||
static bool compositor_present_view_callback(
|
||||
const FlutterPresentViewInfo* info) {
|
||||
g_return_val_if_fail(FL_IS_RENDERER(info->user_data), false);
|
||||
return fl_renderer_present_layers(FL_RENDERER(info->user_data), info->layers,
|
||||
info->layers_count);
|
||||
}
|
||||
|
||||
// Flutter engine rendering callbacks.
|
||||
@ -211,7 +236,7 @@ static uint32_t fl_engine_gl_get_fbo(void* user_data) {
|
||||
|
||||
static bool fl_engine_gl_present(void* user_data) {
|
||||
// No action required, as this is handled in
|
||||
// compositor_present_layers_callback.
|
||||
// compositor_present_view_callback.
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -429,6 +454,9 @@ static void fl_engine_init(FlEngine* self) {
|
||||
self->embedder_api.struct_size = sizeof(FlutterEngineProcTable);
|
||||
FlutterEngineGetProcAddresses(&self->embedder_api);
|
||||
|
||||
// Implicit view is 0, so start at 1.
|
||||
self->next_view_id = 1;
|
||||
|
||||
self->texture_registrar = fl_texture_registrar_new(self);
|
||||
}
|
||||
|
||||
@ -511,7 +539,7 @@ gboolean fl_engine_start(FlEngine* self, GError** error) {
|
||||
compositor_create_backing_store_callback;
|
||||
compositor.collect_backing_store_callback =
|
||||
compositor_collect_backing_store_callback;
|
||||
compositor.present_layers_callback = compositor_present_layers_callback;
|
||||
compositor.present_view_callback = compositor_present_view_callback;
|
||||
args.compositor = &compositor;
|
||||
|
||||
if (self->embedder_api.RunsAOTCompiledDartCode()) {
|
||||
@ -580,6 +608,82 @@ FlutterEngineProcTable* fl_engine_get_embedder_api(FlEngine* self) {
|
||||
return &(self->embedder_api);
|
||||
}
|
||||
|
||||
void fl_engine_add_view(FlEngine* self,
|
||||
size_t width,
|
||||
size_t height,
|
||||
double pixel_ratio,
|
||||
GCancellable* cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data) {
|
||||
g_return_if_fail(FL_IS_ENGINE(self));
|
||||
|
||||
g_autoptr(GTask) task = g_task_new(self, cancellable, callback, user_data);
|
||||
|
||||
FlutterViewId view_id = self->next_view_id;
|
||||
self->next_view_id++;
|
||||
g_task_set_task_data(task, GINT_TO_POINTER(view_id), nullptr);
|
||||
|
||||
FlutterWindowMetricsEvent metrics;
|
||||
metrics.struct_size = sizeof(FlutterWindowMetricsEvent);
|
||||
metrics.width = width;
|
||||
metrics.height = height;
|
||||
metrics.pixel_ratio = pixel_ratio;
|
||||
metrics.view_id = view_id;
|
||||
FlutterAddViewInfo info;
|
||||
info.struct_size = sizeof(FlutterAddViewInfo);
|
||||
info.view_id = view_id;
|
||||
info.view_metrics = &metrics;
|
||||
info.user_data = g_object_ref(task);
|
||||
info.add_view_callback = view_added_cb;
|
||||
FlutterEngineResult result = self->embedder_api.AddView(self->engine, &info);
|
||||
if (result != kSuccess) {
|
||||
g_task_return_new_error(task, fl_engine_error_quark(),
|
||||
FL_ENGINE_ERROR_FAILED, "AddView returned %d",
|
||||
result);
|
||||
// This would have been done in the callback, but that won't occur now.
|
||||
g_object_unref(task);
|
||||
}
|
||||
}
|
||||
|
||||
FlutterViewId fl_engine_add_view_finish(FlEngine* self,
|
||||
GAsyncResult* result,
|
||||
GError** error) {
|
||||
g_return_val_if_fail(FL_IS_ENGINE(self), FALSE);
|
||||
return GPOINTER_TO_INT(g_task_propagate_pointer(G_TASK(result), error));
|
||||
}
|
||||
|
||||
void fl_engine_remove_view(FlEngine* self,
|
||||
FlutterViewId view_id,
|
||||
GCancellable* cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data) {
|
||||
g_return_if_fail(FL_IS_ENGINE(self));
|
||||
|
||||
g_autoptr(GTask) task = g_task_new(self, cancellable, callback, user_data);
|
||||
|
||||
FlutterRemoveViewInfo info;
|
||||
info.struct_size = sizeof(FlutterRemoveViewInfo);
|
||||
info.view_id = view_id;
|
||||
info.user_data = g_object_ref(task);
|
||||
info.remove_view_callback = view_removed_cb;
|
||||
FlutterEngineResult result =
|
||||
self->embedder_api.RemoveView(self->engine, &info);
|
||||
if (result != kSuccess) {
|
||||
g_task_return_new_error(task, fl_engine_error_quark(),
|
||||
FL_ENGINE_ERROR_FAILED, "RemoveView returned %d",
|
||||
result);
|
||||
// This would have been done in the callback, but that won't occur now.
|
||||
g_object_unref(task);
|
||||
}
|
||||
}
|
||||
|
||||
gboolean fl_engine_remove_view_finish(FlEngine* self,
|
||||
GAsyncResult* result,
|
||||
GError** error) {
|
||||
g_return_val_if_fail(FL_IS_ENGINE(self), FALSE);
|
||||
return g_task_propagate_boolean(G_TASK(result), error);
|
||||
}
|
||||
|
||||
void fl_engine_set_platform_message_handler(
|
||||
FlEngine* self,
|
||||
FlEnginePlatformMessageHandler handler,
|
||||
|
||||
@ -81,6 +81,18 @@ typedef void (*FlEngineOnPreEngineRestartHandler)(FlEngine* engine,
|
||||
*/
|
||||
FlEngine* fl_engine_new(FlDartProject* project, FlRenderer* renderer);
|
||||
|
||||
/**
|
||||
* fl_engine_start:
|
||||
* @engine: an #FlEngine.
|
||||
* @error: (allow-none): #GError location to store the error occurring, or %NULL
|
||||
* to ignore.
|
||||
*
|
||||
* Starts the Flutter engine.
|
||||
*
|
||||
* Returns: %TRUE on success.
|
||||
*/
|
||||
gboolean fl_engine_start(FlEngine* engine, GError** error);
|
||||
|
||||
/**
|
||||
* fl_engine_get_embedder_api:
|
||||
* @engine: an #FlEngine.
|
||||
@ -91,6 +103,74 @@ FlEngine* fl_engine_new(FlDartProject* project, FlRenderer* renderer);
|
||||
*/
|
||||
FlutterEngineProcTable* fl_engine_get_embedder_api(FlEngine* engine);
|
||||
|
||||
/**
|
||||
* fl_engine_add_view:
|
||||
* @engine: an #FlEngine.
|
||||
* @width: width of view in pixels.
|
||||
* @height: height of view in pixels.
|
||||
* @pixel_ratio: scale factor for view.
|
||||
* @cancellable: (allow-none): a #GCancellable or %NULL.
|
||||
* @callback: (scope async): a #GAsyncReadyCallback to call when the view is
|
||||
* added.
|
||||
* @user_data: (closure): user data to pass to @callback.
|
||||
*
|
||||
* Asynchronously add a new view.
|
||||
*/
|
||||
void fl_engine_add_view(FlEngine* engine,
|
||||
size_t width,
|
||||
size_t height,
|
||||
double pixel_ratio,
|
||||
GCancellable* cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
|
||||
/**
|
||||
* fl_engine_add_view_finish:
|
||||
* @engine: an #FlEngine.
|
||||
* @result: a #GAsyncResult.
|
||||
* @error: (allow-none): #GError location to store the error occurring, or %NULL
|
||||
* to ignore.
|
||||
*
|
||||
* Completes request started with fl_engine_add_view().
|
||||
*
|
||||
* Returns: the newly added view ID or 0 on error.
|
||||
*/
|
||||
FlutterViewId fl_engine_add_view_finish(FlEngine* engine,
|
||||
GAsyncResult* result,
|
||||
GError** error);
|
||||
|
||||
/**
|
||||
* fl_engine_remove_view:
|
||||
* @engine: an #FlEngine.
|
||||
* @view_id: ID to remove.
|
||||
* @cancellable: (allow-none): a #GCancellable or %NULL.
|
||||
* @callback: (scope async): a #GAsyncReadyCallback to call when the view is
|
||||
* added.
|
||||
* @user_data: (closure): user data to pass to @callback.
|
||||
*
|
||||
* Removes a view previously added with fl_engine_add_view().
|
||||
*/
|
||||
void fl_engine_remove_view(FlEngine* engine,
|
||||
FlutterViewId view_id,
|
||||
GCancellable* cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
|
||||
/**
|
||||
* fl_engine_remove_view_finish:
|
||||
* @engine: an #FlEngine.
|
||||
* @result: a #GAsyncResult.
|
||||
* @error: (allow-none): #GError location to store the error occurring, or %NULL
|
||||
* to ignore.
|
||||
*
|
||||
* Completes request started with fl_engine_remove_view().
|
||||
*
|
||||
* Returns: TRUE on succcess.
|
||||
*/
|
||||
gboolean fl_engine_remove_view_finish(FlEngine* engine,
|
||||
GAsyncResult* result,
|
||||
GError** error);
|
||||
|
||||
/**
|
||||
* fl_engine_set_platform_message_handler:
|
||||
* @engine: an #FlEngine.
|
||||
@ -143,18 +223,6 @@ void fl_engine_set_on_pre_engine_restart_handler(
|
||||
gpointer user_data,
|
||||
GDestroyNotify destroy_notify);
|
||||
|
||||
/**
|
||||
* fl_engine_start:
|
||||
* @engine: an #FlEngine.
|
||||
* @error: (allow-none): #GError location to store the error occurring, or %NULL
|
||||
* to ignore.
|
||||
*
|
||||
* Starts the Flutter engine.
|
||||
*
|
||||
* Returns: %TRUE on success.
|
||||
*/
|
||||
gboolean fl_engine_start(FlEngine* engine, GError** error);
|
||||
|
||||
/**
|
||||
* fl_engine_send_window_metrics_event:
|
||||
* @engine: an #FlEngine.
|
||||
|
||||
@ -425,6 +425,215 @@ TEST(FlEngineTest, SwitchesEmpty) {
|
||||
EXPECT_EQ(switches->len, 0U);
|
||||
}
|
||||
|
||||
static void add_view_cb(GObject* object,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data) {
|
||||
g_autoptr(GError) error = nullptr;
|
||||
FlutterViewId view_id =
|
||||
fl_engine_add_view_finish(FL_ENGINE(object), result, &error);
|
||||
EXPECT_GT(view_id, 0);
|
||||
EXPECT_EQ(error, nullptr);
|
||||
|
||||
g_main_loop_quit(static_cast<GMainLoop*>(user_data));
|
||||
}
|
||||
|
||||
TEST(FlEngineTest, AddView) {
|
||||
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
|
||||
|
||||
g_autoptr(FlEngine) engine = make_mock_engine();
|
||||
FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine);
|
||||
|
||||
bool called = false;
|
||||
embedder_api->AddView = MOCK_ENGINE_PROC(
|
||||
AddView, ([&called](auto engine, const FlutterAddViewInfo* info) {
|
||||
called = true;
|
||||
EXPECT_EQ(info->view_metrics->width, 123u);
|
||||
EXPECT_EQ(info->view_metrics->height, 456u);
|
||||
EXPECT_EQ(info->view_metrics->pixel_ratio, 2.0);
|
||||
|
||||
FlutterAddViewResult result;
|
||||
result.struct_size = sizeof(FlutterAddViewResult);
|
||||
result.added = true;
|
||||
result.user_data = info->user_data;
|
||||
info->add_view_callback(&result);
|
||||
|
||||
return kSuccess;
|
||||
}));
|
||||
|
||||
fl_engine_add_view(engine, 123, 456, 2.0, nullptr, add_view_cb, loop);
|
||||
EXPECT_TRUE(called);
|
||||
|
||||
// Blocks here until add_view_cb is called.
|
||||
g_main_loop_run(loop);
|
||||
}
|
||||
|
||||
static void add_view_error_cb(GObject* object,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data) {
|
||||
g_autoptr(GError) error = nullptr;
|
||||
FlutterViewId view_id =
|
||||
fl_engine_add_view_finish(FL_ENGINE(object), result, &error);
|
||||
EXPECT_EQ(view_id, 0);
|
||||
EXPECT_NE(error, nullptr);
|
||||
|
||||
g_main_loop_quit(static_cast<GMainLoop*>(user_data));
|
||||
}
|
||||
|
||||
TEST(FlEngineTest, AddViewError) {
|
||||
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
|
||||
|
||||
g_autoptr(FlEngine) engine = make_mock_engine();
|
||||
FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine);
|
||||
|
||||
embedder_api->AddView = MOCK_ENGINE_PROC(
|
||||
AddView, ([](auto engine, const FlutterAddViewInfo* info) {
|
||||
FlutterAddViewResult result;
|
||||
result.struct_size = sizeof(FlutterAddViewResult);
|
||||
result.added = false;
|
||||
result.user_data = info->user_data;
|
||||
info->add_view_callback(&result);
|
||||
|
||||
return kSuccess;
|
||||
}));
|
||||
|
||||
fl_engine_add_view(engine, 123, 456, 2.0, nullptr, add_view_error_cb, loop);
|
||||
|
||||
// Blocks here until add_view_error_cb is called.
|
||||
g_main_loop_run(loop);
|
||||
}
|
||||
|
||||
static void add_view_engine_error_cb(GObject* object,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data) {
|
||||
g_autoptr(GError) error = nullptr;
|
||||
FlutterViewId view_id =
|
||||
fl_engine_add_view_finish(FL_ENGINE(object), result, &error);
|
||||
EXPECT_EQ(view_id, 0);
|
||||
EXPECT_NE(error, nullptr);
|
||||
|
||||
g_main_loop_quit(static_cast<GMainLoop*>(user_data));
|
||||
}
|
||||
|
||||
TEST(FlEngineTest, AddViewEngineError) {
|
||||
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
|
||||
|
||||
g_autoptr(FlEngine) engine = make_mock_engine();
|
||||
FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine);
|
||||
|
||||
embedder_api->AddView = MOCK_ENGINE_PROC(
|
||||
AddView, ([](auto engine, const FlutterAddViewInfo* info) {
|
||||
return kInvalidArguments;
|
||||
}));
|
||||
|
||||
fl_engine_add_view(engine, 123, 456, 2.0, nullptr, add_view_engine_error_cb,
|
||||
loop);
|
||||
|
||||
// Blocks here until remove_view_engine_error_cb is called.
|
||||
g_main_loop_run(loop);
|
||||
}
|
||||
|
||||
static void remove_view_cb(GObject* object,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data) {
|
||||
g_autoptr(GError) error = nullptr;
|
||||
gboolean r = fl_engine_remove_view_finish(FL_ENGINE(object), result, &error);
|
||||
EXPECT_TRUE(r);
|
||||
EXPECT_EQ(error, nullptr);
|
||||
|
||||
g_main_loop_quit(static_cast<GMainLoop*>(user_data));
|
||||
}
|
||||
|
||||
TEST(FlEngineTest, RemoveView) {
|
||||
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
|
||||
|
||||
g_autoptr(FlEngine) engine = make_mock_engine();
|
||||
FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine);
|
||||
|
||||
bool called = false;
|
||||
embedder_api->RemoveView = MOCK_ENGINE_PROC(
|
||||
RemoveView, ([&called](auto engine, const FlutterRemoveViewInfo* info) {
|
||||
called = true;
|
||||
EXPECT_EQ(info->view_id, 123);
|
||||
|
||||
FlutterRemoveViewResult result;
|
||||
result.struct_size = sizeof(FlutterRemoveViewResult);
|
||||
result.removed = true;
|
||||
result.user_data = info->user_data;
|
||||
info->remove_view_callback(&result);
|
||||
|
||||
return kSuccess;
|
||||
}));
|
||||
|
||||
fl_engine_remove_view(engine, 123, nullptr, remove_view_cb, loop);
|
||||
EXPECT_TRUE(called);
|
||||
|
||||
// Blocks here until remove_view_cb is called.
|
||||
g_main_loop_run(loop);
|
||||
}
|
||||
|
||||
static void remove_view_error_cb(GObject* object,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data) {
|
||||
g_autoptr(GError) error = nullptr;
|
||||
gboolean r = fl_engine_remove_view_finish(FL_ENGINE(object), result, &error);
|
||||
EXPECT_FALSE(r);
|
||||
EXPECT_NE(error, nullptr);
|
||||
|
||||
g_main_loop_quit(static_cast<GMainLoop*>(user_data));
|
||||
}
|
||||
|
||||
TEST(FlEngineTest, RemoveViewError) {
|
||||
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
|
||||
|
||||
g_autoptr(FlEngine) engine = make_mock_engine();
|
||||
FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine);
|
||||
|
||||
embedder_api->RemoveView = MOCK_ENGINE_PROC(
|
||||
RemoveView, ([](auto engine, const FlutterRemoveViewInfo* info) {
|
||||
FlutterRemoveViewResult result;
|
||||
result.struct_size = sizeof(FlutterRemoveViewResult);
|
||||
result.removed = false;
|
||||
result.user_data = info->user_data;
|
||||
info->remove_view_callback(&result);
|
||||
|
||||
return kSuccess;
|
||||
}));
|
||||
|
||||
fl_engine_remove_view(engine, 123, nullptr, remove_view_error_cb, loop);
|
||||
|
||||
// Blocks here until remove_view_error_cb is called.
|
||||
g_main_loop_run(loop);
|
||||
}
|
||||
|
||||
static void remove_view_engine_error_cb(GObject* object,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data) {
|
||||
g_autoptr(GError) error = nullptr;
|
||||
gboolean r = fl_engine_remove_view_finish(FL_ENGINE(object), result, &error);
|
||||
EXPECT_FALSE(r);
|
||||
EXPECT_NE(error, nullptr);
|
||||
|
||||
g_main_loop_quit(static_cast<GMainLoop*>(user_data));
|
||||
}
|
||||
|
||||
TEST(FlEngineTest, RemoveViewEngineError) {
|
||||
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
|
||||
|
||||
g_autoptr(FlEngine) engine = make_mock_engine();
|
||||
FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine);
|
||||
|
||||
embedder_api->RemoveView = MOCK_ENGINE_PROC(
|
||||
RemoveView, ([](auto engine, const FlutterRemoveViewInfo* info) {
|
||||
return kInvalidArguments;
|
||||
}));
|
||||
|
||||
fl_engine_remove_view(engine, 123, nullptr, remove_view_engine_error_cb,
|
||||
loop);
|
||||
|
||||
// Blocks here until remove_view_engine_error_cb is called.
|
||||
g_main_loop_run(loop);
|
||||
}
|
||||
|
||||
#ifndef FLUTTER_RELEASE
|
||||
TEST(FlEngineTest, Switches) {
|
||||
g_autoptr(FlEngine) engine = make_mock_engine();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user