[Embedder] Wire view focus event and focus request (#163930)
This wires `PlatformDispatcher.onViewFocusChange` and `PlatformDispatches.requestViewFocusChange` through embedder API. *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I readand followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Matthew Kosarek <matt.kosarek@canonical.com> Co-authored-by: Loïc Sharma <737941+loic-sharma@users.noreply.github.com>
This commit is contained in:
parent
289993ab83
commit
940fb3c8b1
@ -42555,6 +42555,8 @@ ORIGIN: ../../../flutter/lib/ui/window/pointer_data_packet.cc + ../../../flutter
|
||||
ORIGIN: ../../../flutter/lib/ui/window/pointer_data_packet.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/ui/window/pointer_data_packet_converter.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/ui/window/pointer_data_packet_converter.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/ui/window/view_focus.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/ui/window/view_focus.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/ui/window/viewport_metrics.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/ui/window/viewport_metrics.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/flutter_js/src/browser_environment.js + ../../../flutter/LICENSE
|
||||
@ -45471,6 +45473,8 @@ FILE: ../../../flutter/lib/ui/window/pointer_data_packet.cc
|
||||
FILE: ../../../flutter/lib/ui/window/pointer_data_packet.h
|
||||
FILE: ../../../flutter/lib/ui/window/pointer_data_packet_converter.cc
|
||||
FILE: ../../../flutter/lib/ui/window/pointer_data_packet_converter.h
|
||||
FILE: ../../../flutter/lib/ui/window/view_focus.cc
|
||||
FILE: ../../../flutter/lib/ui/window/view_focus.h
|
||||
FILE: ../../../flutter/lib/ui/window/viewport_metrics.cc
|
||||
FILE: ../../../flutter/lib/ui/window/viewport_metrics.h
|
||||
FILE: ../../../flutter/lib/web_ui/flutter_js/src/browser_environment.js
|
||||
|
@ -156,6 +156,8 @@ source_set("ui") {
|
||||
"window/pointer_data_packet.h",
|
||||
"window/pointer_data_packet_converter.cc",
|
||||
"window/pointer_data_packet_converter.h",
|
||||
"window/view_focus.cc",
|
||||
"window/view_focus.h",
|
||||
"window/viewport_metrics.cc",
|
||||
"window/viewport_metrics.h",
|
||||
]
|
||||
|
@ -107,6 +107,7 @@ typedef CanvasPath Path;
|
||||
V(PlatformConfigurationNativeApi::GetRootIsolateToken) \
|
||||
V(PlatformConfigurationNativeApi::RegisterBackgroundIsolate) \
|
||||
V(PlatformConfigurationNativeApi::SendPortPlatformMessage) \
|
||||
V(PlatformConfigurationNativeApi::RequestViewFocusChange) \
|
||||
V(PlatformConfigurationNativeApi::SendChannelUpdate) \
|
||||
V(PlatformConfigurationNativeApi::GetScaledFontSize) \
|
||||
V(PlatformIsolateNativeApi::IsRunningOnPlatformThread) \
|
||||
|
@ -58,6 +58,16 @@ void _removeView(int viewId) {
|
||||
PlatformDispatcher.instance._removeView(viewId);
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void _sendViewFocusEvent(int viewId, int viewFocusState, int viewFocusDirection) {
|
||||
final ViewFocusEvent viewFocusEvent = ViewFocusEvent(
|
||||
viewId: viewId,
|
||||
state: ViewFocusState.values[viewFocusState],
|
||||
direction: ViewFocusDirection.values[viewFocusDirection],
|
||||
);
|
||||
PlatformDispatcher.instance._sendViewFocusEvent(viewFocusEvent);
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void _updateDisplays(
|
||||
List<int> ids,
|
||||
|
@ -301,6 +301,10 @@ class PlatformDispatcher {
|
||||
_invoke(onMetricsChanged, _onMetricsChangedZone);
|
||||
}
|
||||
|
||||
void _sendViewFocusEvent(ViewFocusEvent event) {
|
||||
_invoke1<ViewFocusEvent>(onViewFocusChange, _onViewFocusChangeZone, event);
|
||||
}
|
||||
|
||||
// Called from the engine, via hooks.dart.
|
||||
//
|
||||
// Updates the available displays.
|
||||
@ -384,9 +388,14 @@ class PlatformDispatcher {
|
||||
required ViewFocusState state,
|
||||
required ViewFocusDirection direction,
|
||||
}) {
|
||||
// TODO(tugorez): implement this method. At the moment will be a no op call.
|
||||
_requestViewFocusChange(viewId, state.index, direction.index);
|
||||
}
|
||||
|
||||
@Native<Void Function(Int64, Int64, Int64)>(
|
||||
symbol: 'PlatformConfigurationNativeApi::RequestViewFocusChange',
|
||||
)
|
||||
external static void _requestViewFocusChange(int viewId, int state, int direction);
|
||||
|
||||
/// A callback invoked when any view begins a frame.
|
||||
///
|
||||
/// A callback that is invoked to notify the application that it is an
|
||||
|
@ -46,6 +46,9 @@ void PlatformConfiguration::DidCreateIsolate() {
|
||||
Dart_GetField(library, tonic::ToDart("_addView")));
|
||||
remove_view_.Set(tonic::DartState::Current(),
|
||||
Dart_GetField(library, tonic::ToDart("_removeView")));
|
||||
send_view_focus_event_.Set(
|
||||
tonic::DartState::Current(),
|
||||
Dart_GetField(library, tonic::ToDart("_sendViewFocusEvent")));
|
||||
update_window_metrics_.Set(
|
||||
tonic::DartState::Current(),
|
||||
Dart_GetField(library, tonic::ToDart("_updateWindowMetrics")));
|
||||
@ -149,6 +152,22 @@ bool PlatformConfiguration::RemoveView(int64_t view_id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlatformConfiguration::SendFocusEvent(const ViewFocusEvent& event) {
|
||||
std::shared_ptr<tonic::DartState> dart_state =
|
||||
remove_view_.dart_state().lock();
|
||||
if (!dart_state) {
|
||||
return false;
|
||||
}
|
||||
tonic::DartState::Scope scope(dart_state);
|
||||
tonic::CheckAndHandleError(tonic::DartInvoke(
|
||||
send_view_focus_event_.Get(), {
|
||||
tonic::ToDart(event.view_id()),
|
||||
tonic::ToDart(event.state()),
|
||||
tonic::ToDart(event.direction()),
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlatformConfiguration::UpdateViewMetrics(
|
||||
int64_t view_id,
|
||||
const ViewportMetrics& view_metrics) {
|
||||
@ -552,6 +571,17 @@ Dart_Handle PlatformConfigurationNativeApi::SendPortPlatformMessage(
|
||||
return HandlePlatformMessage(dart_state, name, data_handle, response);
|
||||
}
|
||||
|
||||
void PlatformConfigurationNativeApi::RequestViewFocusChange(int64_t view_id,
|
||||
int64_t state,
|
||||
int64_t direction) {
|
||||
ViewFocusChangeRequest request{view_id, //
|
||||
static_cast<ViewFocusState>(state),
|
||||
static_cast<ViewFocusDirection>(direction)};
|
||||
UIDartState* dart_state = UIDartState::Current();
|
||||
dart_state->platform_configuration()->client()->RequestViewFocusChange(
|
||||
request);
|
||||
}
|
||||
|
||||
void PlatformConfigurationNativeApi::RespondToPlatformMessage(
|
||||
int response_id,
|
||||
const tonic::DartByteData& data) {
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "flutter/lib/ui/semantics/semantics_update.h"
|
||||
#include "flutter/lib/ui/window/platform_message_response.h"
|
||||
#include "flutter/lib/ui/window/pointer_data_packet.h"
|
||||
#include "flutter/lib/ui/window/view_focus.h"
|
||||
#include "flutter/lib/ui/window/viewport_metrics.h"
|
||||
#include "flutter/shell/common/display.h"
|
||||
#include "fml/macros.h"
|
||||
@ -257,6 +258,14 @@ class PlatformConfigurationClient {
|
||||
virtual double GetScaledFontSize(double unscaled_font_size,
|
||||
int configuration_id) const = 0;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// @brief Notifies the client that the Flutter view focus state has
|
||||
/// changed and the platform view should be updated.
|
||||
///
|
||||
/// @param[in] request The request to change the focus state of the view.
|
||||
virtual void RequestViewFocusChange(
|
||||
const ViewFocusChangeRequest& request) = 0;
|
||||
|
||||
virtual std::shared_ptr<PlatformIsolateManager>
|
||||
GetPlatformIsolateManager() = 0;
|
||||
|
||||
@ -338,6 +347,15 @@ class PlatformConfiguration final {
|
||||
///
|
||||
bool RemoveView(int64_t view_id);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Notify the isolate that the focus state of a native view has
|
||||
/// changed.
|
||||
///
|
||||
/// @param[in] event The focus event describing the change.
|
||||
///
|
||||
/// @return Whether the focus event was sent.
|
||||
bool SendFocusEvent(const ViewFocusEvent& event);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Update the view metrics for the specified view.
|
||||
///
|
||||
@ -529,6 +547,7 @@ class PlatformConfiguration final {
|
||||
tonic::DartPersistentValue on_error_;
|
||||
tonic::DartPersistentValue add_view_;
|
||||
tonic::DartPersistentValue remove_view_;
|
||||
tonic::DartPersistentValue send_view_focus_event_;
|
||||
tonic::DartPersistentValue update_window_metrics_;
|
||||
tonic::DartPersistentValue update_displays_;
|
||||
tonic::DartPersistentValue update_locales_;
|
||||
@ -617,6 +636,10 @@ class PlatformConfigurationNativeApi {
|
||||
|
||||
static void SendChannelUpdate(const std::string& name, bool listening);
|
||||
|
||||
static void RequestViewFocusChange(int64_t view_id,
|
||||
int64_t state,
|
||||
int64_t direction);
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// @brief Requests the Dart VM to adjusts the GC heuristics based on
|
||||
/// the requested `performance_mode`. Returns the old performance
|
||||
|
23
engine/src/flutter/lib/ui/window/view_focus.cc
Normal file
23
engine/src/flutter/lib/ui/window/view_focus.cc
Normal file
@ -0,0 +1,23 @@
|
||||
// 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/lib/ui/window/view_focus.h"
|
||||
|
||||
namespace flutter {
|
||||
ViewFocusChangeRequest::ViewFocusChangeRequest(int64_t view_id,
|
||||
ViewFocusState state,
|
||||
ViewFocusDirection direction)
|
||||
: view_id_(view_id), state_(state), direction_(direction) {}
|
||||
|
||||
int64_t ViewFocusChangeRequest::view_id() const {
|
||||
return view_id_;
|
||||
}
|
||||
ViewFocusState ViewFocusChangeRequest::state() const {
|
||||
return state_;
|
||||
}
|
||||
ViewFocusDirection ViewFocusChangeRequest::direction() const {
|
||||
return direction_;
|
||||
}
|
||||
|
||||
} // namespace flutter
|
69
engine/src/flutter/lib/ui/window/view_focus.h
Normal file
69
engine/src/flutter/lib/ui/window/view_focus.h
Normal file
@ -0,0 +1,69 @@
|
||||
// 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_LIB_UI_WINDOW_VIEW_FOCUS_H_
|
||||
#define FLUTTER_LIB_UI_WINDOW_VIEW_FOCUS_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace flutter {
|
||||
|
||||
// Focus state of a View.
|
||||
// Must match ViewFocusState in ui/platform_dispatcher.dart.
|
||||
enum class ViewFocusState : int64_t {
|
||||
kUnfocused = 0,
|
||||
kFocused,
|
||||
};
|
||||
|
||||
// Represents the direction of which the focus transitioned over
|
||||
// a FlutterView.
|
||||
// Must match ViewFocusDirection in ui/platform_dispatcher.dart.
|
||||
enum class ViewFocusDirection : int64_t {
|
||||
kUndefined = 0,
|
||||
kForward,
|
||||
kBackward,
|
||||
};
|
||||
|
||||
// Event sent by the embedder to the engine indicating that native view focus
|
||||
// state has changed.
|
||||
class ViewFocusEvent {
|
||||
public:
|
||||
ViewFocusEvent(int64_t view_id,
|
||||
ViewFocusState state,
|
||||
ViewFocusDirection direction)
|
||||
: view_id_(view_id), state_(state), direction_(direction) {}
|
||||
|
||||
int64_t view_id() const { return view_id_; }
|
||||
ViewFocusState state() const { return state_; }
|
||||
ViewFocusDirection direction() const { return direction_; }
|
||||
|
||||
private:
|
||||
int64_t view_id_;
|
||||
ViewFocusState state_;
|
||||
ViewFocusDirection direction_;
|
||||
};
|
||||
|
||||
// Request sent by the engine to the embedder indicating that the FlutterView
|
||||
// focus state has changed and the native view should be updated.
|
||||
class ViewFocusChangeRequest {
|
||||
public:
|
||||
ViewFocusChangeRequest(int64_t view_id,
|
||||
ViewFocusState state,
|
||||
ViewFocusDirection direction);
|
||||
|
||||
int64_t view_id() const;
|
||||
ViewFocusState state() const;
|
||||
ViewFocusDirection direction() const;
|
||||
|
||||
private:
|
||||
ViewFocusChangeRequest() = delete;
|
||||
|
||||
int64_t view_id_ = 0;
|
||||
ViewFocusState state_ = ViewFocusState::kUnfocused;
|
||||
ViewFocusDirection direction_ = ViewFocusDirection::kUndefined;
|
||||
};
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
#endif // FLUTTER_LIB_UI_WINDOW_VIEW_FOCUS_H_
|
@ -735,6 +735,7 @@ class FakePlatformConfigurationClient : public PlatformConfigurationClient {
|
||||
int configuration_id) const override {
|
||||
return 0;
|
||||
}
|
||||
void RequestViewFocusChange(const ViewFocusChangeRequest& request) override {}
|
||||
};
|
||||
|
||||
TEST_F(DartIsolateTest, PlatformIsolateCreationAndShutdown) {
|
||||
|
@ -209,6 +209,14 @@ bool RuntimeController::RemoveView(int64_t view_id) {
|
||||
return platform_configuration->RemoveView(view_id);
|
||||
}
|
||||
|
||||
bool RuntimeController::SendViewFocusEvent(const ViewFocusEvent& event) {
|
||||
auto* platform_configuration = GetPlatformConfigurationIfAvailable();
|
||||
if (!platform_configuration) {
|
||||
return false;
|
||||
}
|
||||
return platform_configuration->SendFocusEvent(event);
|
||||
}
|
||||
|
||||
bool RuntimeController::ViewExists(int64_t view_id) const {
|
||||
return platform_data_.viewport_metrics_for_views.count(view_id) != 0;
|
||||
}
|
||||
@ -649,6 +657,11 @@ double RuntimeController::GetScaledFontSize(double unscaled_font_size,
|
||||
return client_.GetScaledFontSize(unscaled_font_size, configuration_id);
|
||||
}
|
||||
|
||||
void RuntimeController::RequestViewFocusChange(
|
||||
const ViewFocusChangeRequest& request) {
|
||||
client_.RequestViewFocusChange(request);
|
||||
}
|
||||
|
||||
void RuntimeController::ShutdownPlatformIsolates() {
|
||||
platform_isolate_manager_->ShutdownPlatformIsolates();
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "flutter/lib/ui/window/platform_configuration.h"
|
||||
#include "flutter/lib/ui/window/pointer_data_packet.h"
|
||||
#include "flutter/lib/ui/window/pointer_data_packet_converter.h"
|
||||
#include "flutter/lib/ui/window/view_focus.h"
|
||||
#include "flutter/runtime/dart_vm.h"
|
||||
#include "flutter/runtime/platform_data.h"
|
||||
#include "flutter/runtime/platform_isolate_manager.h"
|
||||
@ -224,6 +225,13 @@ class RuntimeController : public PlatformConfigurationClient,
|
||||
/// cancelled and the return value is always false.
|
||||
bool RemoveView(int64_t view_id);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Notify the isolate that the focus state of a native view has
|
||||
/// changed.
|
||||
///
|
||||
/// @param[in] event The focus event describing the change.
|
||||
bool SendViewFocusEvent(const ViewFocusEvent& event);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Forward the specified viewport metrics to the running isolate.
|
||||
/// If the isolate is not running, these metrics will be saved and
|
||||
@ -785,6 +793,9 @@ class RuntimeController : public PlatformConfigurationClient,
|
||||
double GetScaledFontSize(double unscaled_font_size,
|
||||
int configuration_id) const override;
|
||||
|
||||
// |PlatformConfigurationClient|
|
||||
void RequestViewFocusChange(const ViewFocusChangeRequest& request) override;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(RuntimeController);
|
||||
};
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "flutter/lib/ui/semantics/semantics_node.h"
|
||||
#include "flutter/lib/ui/text/font_collection.h"
|
||||
#include "flutter/lib/ui/window/platform_message.h"
|
||||
#include "flutter/lib/ui/window/view_focus.h"
|
||||
#include "flutter/shell/common/platform_message_handler.h"
|
||||
#include "third_party/dart/runtime/include/dart_api.h"
|
||||
|
||||
@ -62,6 +63,9 @@ class RuntimeDelegate {
|
||||
virtual double GetScaledFontSize(double unscaled_font_size,
|
||||
int configuration_id) const = 0;
|
||||
|
||||
virtual void RequestViewFocusChange(
|
||||
const ViewFocusChangeRequest& request) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~RuntimeDelegate();
|
||||
};
|
||||
|
@ -312,6 +312,10 @@ bool Engine::RemoveView(int64_t view_id) {
|
||||
return runtime_controller_->RemoveView(view_id);
|
||||
}
|
||||
|
||||
bool Engine::SendViewFocusEvent(const ViewFocusEvent& event) {
|
||||
return runtime_controller_->SendViewFocusEvent(event);
|
||||
}
|
||||
|
||||
void Engine::SetViewportMetrics(int64_t view_id,
|
||||
const ViewportMetrics& metrics) {
|
||||
runtime_controller_->SetViewportMetrics(view_id, metrics);
|
||||
@ -522,6 +526,10 @@ double Engine::GetScaledFontSize(double unscaled_font_size,
|
||||
return delegate_.GetScaledFontSize(unscaled_font_size, configuration_id);
|
||||
}
|
||||
|
||||
void Engine::RequestViewFocusChange(const ViewFocusChangeRequest& request) {
|
||||
delegate_.RequestViewFocusChange(request);
|
||||
}
|
||||
|
||||
void Engine::SetNeedsReportTimings(bool needs_reporting) {
|
||||
delegate_.SetNeedsReportTimings(needs_reporting);
|
||||
}
|
||||
|
@ -323,6 +323,14 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
|
||||
///
|
||||
virtual double GetScaledFontSize(double unscaled_font_size,
|
||||
int configuration_id) const = 0;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// @brief Notifies the client that the Flutter view focus state has
|
||||
/// changed and the platform view should be updated.
|
||||
///
|
||||
/// @param[in] request The request to change the focus state of the view.
|
||||
virtual void RequestViewFocusChange(
|
||||
const ViewFocusChangeRequest& request) = 0;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@ -747,6 +755,13 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
|
||||
///
|
||||
bool RemoveView(int64_t view_id);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Notify the Flutter application that the focus state of a
|
||||
/// native view has changed.
|
||||
///
|
||||
/// @param[in] event The focus event describing the change.
|
||||
bool SendViewFocusEvent(const ViewFocusEvent& event);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Updates the viewport metrics for a view. The viewport metrics
|
||||
/// detail the size of the rendering viewport in texels as well as
|
||||
@ -1024,6 +1039,9 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
|
||||
double GetScaledFontSize(double unscaled_font_size,
|
||||
int configuration_id) const override;
|
||||
|
||||
// |RuntimeDelegate|
|
||||
void RequestViewFocusChange(const ViewFocusChangeRequest& request) override;
|
||||
|
||||
void SetNeedsReportTimings(bool value) override;
|
||||
|
||||
bool HandleLifecyclePlatformMessage(PlatformMessage* message);
|
||||
|
@ -82,6 +82,10 @@ class MockDelegate : public Engine::Delegate {
|
||||
GetScaledFontSize,
|
||||
(double font_size, int configuration_id),
|
||||
(const, override));
|
||||
MOCK_METHOD(void,
|
||||
RequestViewFocusChange,
|
||||
(const ViewFocusChangeRequest&),
|
||||
(override));
|
||||
};
|
||||
|
||||
class MockAnimatorDelegate : public Animator::Delegate {
|
||||
|
@ -90,6 +90,10 @@ class MockDelegate : public Engine::Delegate {
|
||||
GetScaledFontSize,
|
||||
(double font_size, int configuration_id),
|
||||
(const, override));
|
||||
MOCK_METHOD(void,
|
||||
RequestViewFocusChange,
|
||||
(const ViewFocusChangeRequest&),
|
||||
(override));
|
||||
};
|
||||
|
||||
class MockResponse : public PlatformMessageResponse {
|
||||
@ -137,6 +141,10 @@ class MockRuntimeDelegate : public RuntimeDelegate {
|
||||
GetScaledFontSize,
|
||||
(double font_size, int configuration_id),
|
||||
(const, override));
|
||||
MOCK_METHOD(void,
|
||||
RequestViewFocusChange,
|
||||
(const ViewFocusChangeRequest&),
|
||||
(override));
|
||||
};
|
||||
|
||||
class MockRuntimeController : public RuntimeController {
|
||||
|
@ -632,3 +632,11 @@ void testDispatchEvents() {
|
||||
notifyNative();
|
||||
};
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void testSendViewFocusEvent() {
|
||||
PlatformDispatcher.instance.onViewFocusChange = (ViewFocusEvent event) {
|
||||
notifyMessage('${event.viewId} ${event.state} ${event.direction}');
|
||||
};
|
||||
notifyNative();
|
||||
}
|
||||
|
@ -97,6 +97,10 @@ void PlatformView::RemoveView(int64_t view_id, RemoveViewCallback callback) {
|
||||
delegate_.OnPlatformViewRemoveView(view_id, std::move(callback));
|
||||
}
|
||||
|
||||
void PlatformView::SendViewFocusEvent(const ViewFocusEvent& event) {
|
||||
delegate_.OnPlatformViewSendViewFocusEvent(event);
|
||||
}
|
||||
|
||||
sk_sp<GrDirectContext> PlatformView::CreateResourceContext() const {
|
||||
FML_DLOG(WARNING) << "This platform does not set up the resource "
|
||||
"context on the IO thread for async texture uploads.";
|
||||
@ -219,4 +223,9 @@ double PlatformView::GetScaledFontSize(double unscaled_font_size,
|
||||
return -1;
|
||||
}
|
||||
|
||||
void PlatformView::RequestViewFocusChange(
|
||||
const ViewFocusChangeRequest& request) {
|
||||
// No-op by default.
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
|
@ -121,6 +121,12 @@ class PlatformView {
|
||||
virtual void OnPlatformViewRemoveView(int64_t view_id,
|
||||
RemoveViewCallback callback) = 0;
|
||||
|
||||
/// @brief Notify the delegate that platform view focus state has changed.
|
||||
///
|
||||
/// @param[in] event The focus event describing the change.
|
||||
virtual void OnPlatformViewSendViewFocusEvent(
|
||||
const ViewFocusEvent& event) = 0;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// @brief Notifies the delegate that the specified callback needs to
|
||||
/// be invoked after the rasterizer is done rendering the next
|
||||
@ -605,6 +611,8 @@ class PlatformView {
|
||||
///
|
||||
void RemoveView(int64_t view_id, RemoveViewCallback callback);
|
||||
|
||||
void SendViewFocusEvent(const ViewFocusEvent& event);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Used by the shell to obtain a Skia GPU context that is capable
|
||||
/// of operating on the IO thread. The context must be in the same
|
||||
@ -954,6 +962,15 @@ class PlatformView {
|
||||
virtual double GetScaledFontSize(double unscaled_font_size,
|
||||
int configuration_id) const;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// @brief Notifies the client that the Flutter view focus state has
|
||||
/// changed and the platform view should be updated.
|
||||
///
|
||||
/// Called on platform thread.
|
||||
///
|
||||
/// @param[in] request The request to change the focus state of the view.
|
||||
virtual void RequestViewFocusChange(const ViewFocusChangeRequest& request);
|
||||
|
||||
protected:
|
||||
// This is the only method called on the raster task runner.
|
||||
virtual std::unique_ptr<Surface> CreateRenderingSurface();
|
||||
|
@ -1550,6 +1550,18 @@ double Shell::GetScaledFontSize(double unscaled_font_size,
|
||||
configuration_id);
|
||||
}
|
||||
|
||||
void Shell::RequestViewFocusChange(const ViewFocusChangeRequest& request) {
|
||||
FML_DCHECK(is_set_up_);
|
||||
|
||||
fml::TaskRunner::RunNowOrPostTask(
|
||||
task_runners_.GetPlatformTaskRunner(),
|
||||
[view = platform_view_->GetWeakPtr(), request] {
|
||||
if (view) {
|
||||
view->RequestViewFocusChange(request);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Shell::ReportTimings() {
|
||||
FML_DCHECK(is_set_up_);
|
||||
FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread());
|
||||
@ -2102,6 +2114,20 @@ void Shell::OnPlatformViewRemoveView(int64_t view_id,
|
||||
});
|
||||
}
|
||||
|
||||
void Shell::OnPlatformViewSendViewFocusEvent(const ViewFocusEvent& event) {
|
||||
TRACE_EVENT0("flutter", "Shell:: OnPlatformViewSendViewFocusEvent");
|
||||
FML_DCHECK(is_set_up_);
|
||||
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
||||
|
||||
task_runners_.GetUITaskRunner()->RunNowOrPostTask(
|
||||
task_runners_.GetUITaskRunner(),
|
||||
[engine = engine_->GetWeakPtr(), event = event] {
|
||||
if (engine) {
|
||||
engine->SendViewFocusEvent(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Rasterizer::Screenshot Shell::Screenshot(
|
||||
Rasterizer::ScreenshotType screenshot_type,
|
||||
bool base64_encode) {
|
||||
|
@ -588,6 +588,9 @@ class Shell final : public PlatformView::Delegate,
|
||||
void OnPlatformViewRemoveView(int64_t view_id,
|
||||
RemoveViewCallback callback) override;
|
||||
|
||||
// |PlatformView::Delegate|
|
||||
void OnPlatformViewSendViewFocusEvent(const ViewFocusEvent& event) override;
|
||||
|
||||
// |PlatformView::Delegate|
|
||||
void OnPlatformViewSetViewportMetrics(
|
||||
int64_t view_id,
|
||||
@ -702,6 +705,9 @@ class Shell final : public PlatformView::Delegate,
|
||||
double GetScaledFontSize(double unscaled_font_size,
|
||||
int configuration_id) const override;
|
||||
|
||||
// |Engine::Delegate|
|
||||
void RequestViewFocusChange(const ViewFocusChangeRequest& request) override;
|
||||
|
||||
// |Rasterizer::Delegate|
|
||||
void OnFrameRasterized(const FrameTiming&) override;
|
||||
|
||||
|
@ -116,6 +116,11 @@ class MockPlatformViewDelegate : public PlatformView::Delegate {
|
||||
(int64_t view_id, RemoveViewCallback callback),
|
||||
(override));
|
||||
|
||||
MOCK_METHOD(void,
|
||||
OnPlatformViewSendViewFocusEvent,
|
||||
(const ViewFocusEvent& event),
|
||||
(override));
|
||||
|
||||
MOCK_METHOD(void,
|
||||
OnPlatformViewSetNextFrameCallback,
|
||||
(const fml::closure& closure),
|
||||
@ -4952,6 +4957,56 @@ TEST_F(ShellTest, WillLogWarningWhenImpellerIsOptedOut) {
|
||||
DestroyShell(std::move(shell), task_runners);
|
||||
}
|
||||
|
||||
TEST_F(ShellTest, SendViewFocusEvent) {
|
||||
Settings settings = CreateSettingsForFixture();
|
||||
TaskRunners task_runners = GetTaskRunnersForFixture();
|
||||
fml::AutoResetWaitableEvent latch;
|
||||
std::string last_event;
|
||||
|
||||
AddNativeCallback(
|
||||
"NotifyNative",
|
||||
CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); }));
|
||||
|
||||
AddNativeCallback("NotifyMessage",
|
||||
CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
|
||||
const auto message_from_dart =
|
||||
tonic::DartConverter<std::string>::FromDart(
|
||||
Dart_GetNativeArgument(args, 0));
|
||||
last_event = message_from_dart;
|
||||
latch.Signal();
|
||||
}));
|
||||
fml::AutoResetWaitableEvent check_latch;
|
||||
|
||||
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
|
||||
ASSERT_TRUE(shell->IsSetup());
|
||||
|
||||
auto configuration = RunConfiguration::InferFromSettings(settings);
|
||||
|
||||
configuration.SetEntrypoint("testSendViewFocusEvent");
|
||||
RunEngine(shell.get(), std::move(configuration));
|
||||
latch.Wait();
|
||||
latch.Reset();
|
||||
|
||||
PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
|
||||
shell->GetPlatformView()->SendViewFocusEvent(ViewFocusEvent(
|
||||
1, ViewFocusState::kFocused, ViewFocusDirection::kUndefined));
|
||||
});
|
||||
latch.Wait();
|
||||
ASSERT_EQ(last_event,
|
||||
"1 ViewFocusState.focused ViewFocusDirection.undefined");
|
||||
|
||||
latch.Reset();
|
||||
PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
|
||||
shell->GetPlatformView()->SendViewFocusEvent(ViewFocusEvent(
|
||||
2, ViewFocusState::kUnfocused, ViewFocusDirection::kBackward));
|
||||
});
|
||||
latch.Wait();
|
||||
ASSERT_EQ(last_event,
|
||||
"2 ViewFocusState.unfocused ViewFocusDirection.backward");
|
||||
|
||||
DestroyShell(std::move(shell), task_runners);
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace flutter
|
||||
|
||||
|
@ -28,6 +28,7 @@ class FakeDelegate : public PlatformView::Delegate {
|
||||
const ViewportMetrics& viewport_metrics,
|
||||
AddViewCallback callback) override {}
|
||||
void OnPlatformViewRemoveView(int64_t view_id, RemoveViewCallback callback) override {}
|
||||
void OnPlatformViewSendViewFocusEvent(const ViewFocusEvent& event) override {}
|
||||
void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override {}
|
||||
void OnPlatformViewSetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics) override {}
|
||||
const flutter::Settings& OnPlatformViewGetSettings() const override { return settings_; }
|
||||
|
@ -263,6 +263,7 @@ class FlutterPlatformViewsTestMockPlatformViewDelegate : public PlatformView::De
|
||||
const ViewportMetrics& viewport_metrics,
|
||||
AddViewCallback callback) override {}
|
||||
void OnPlatformViewRemoveView(int64_t view_id, RemoveViewCallback callback) override {}
|
||||
void OnPlatformViewSendViewFocusEvent(const ViewFocusEvent& event) override {};
|
||||
void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override {}
|
||||
void OnPlatformViewSetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics) override {}
|
||||
const flutter::Settings& OnPlatformViewGetSettings() const override { return settings_; }
|
||||
|
@ -75,6 +75,7 @@ class MockDelegate : public PlatformView::Delegate {
|
||||
const ViewportMetrics& viewport_metrics,
|
||||
AddViewCallback callback) override {}
|
||||
void OnPlatformViewRemoveView(int64_t view_id, RemoveViewCallback callback) override {}
|
||||
void OnPlatformViewSendViewFocusEvent(const ViewFocusEvent& event) override {};
|
||||
void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override {}
|
||||
void OnPlatformViewSetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics) override {}
|
||||
const flutter::Settings& OnPlatformViewGetSettings() const override { return settings_; }
|
||||
|
@ -2226,6 +2226,24 @@ FlutterEngineResult FlutterEngineInitialize(size_t version,
|
||||
};
|
||||
}
|
||||
|
||||
flutter::PlatformViewEmbedder::ViewFocusChangeRequestCallback
|
||||
view_focus_change_request_callback = nullptr;
|
||||
if (SAFE_ACCESS(args, view_focus_change_request_callback, nullptr) !=
|
||||
nullptr) {
|
||||
view_focus_change_request_callback =
|
||||
[ptr = args->view_focus_change_request_callback,
|
||||
user_data](const flutter::ViewFocusChangeRequest& request) {
|
||||
FlutterViewFocusChangeRequest embedder_request{
|
||||
.struct_size = sizeof(FlutterViewFocusChangeRequest),
|
||||
.view_id = request.view_id(),
|
||||
.state = static_cast<FlutterViewFocusState>(request.state()),
|
||||
.direction =
|
||||
static_cast<FlutterViewFocusDirection>(request.direction()),
|
||||
};
|
||||
ptr(&embedder_request, user_data);
|
||||
};
|
||||
}
|
||||
|
||||
auto external_view_embedder_result = InferExternalViewEmbedderFromArgs(
|
||||
SAFE_ACCESS(args, compositor, nullptr), settings.enable_impeller);
|
||||
if (external_view_embedder_result.second) {
|
||||
@ -2241,6 +2259,7 @@ FlutterEngineResult FlutterEngineInitialize(size_t version,
|
||||
compute_platform_resolved_locale_callback, //
|
||||
on_pre_engine_restart_callback, //
|
||||
channel_update_callback, //
|
||||
view_focus_change_request_callback, //
|
||||
};
|
||||
|
||||
auto on_create_platform_view = InferPlatformViewCreationCallback(
|
||||
@ -2556,6 +2575,38 @@ FlutterEngineResult FlutterEngineRemoveView(FLUTTER_API_SYMBOL(FlutterEngine)
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
FlutterEngineResult FlutterEngineSendViewFocusEvent(
|
||||
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
||||
const FlutterViewFocusEvent* event) {
|
||||
if (!engine) {
|
||||
return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid.");
|
||||
}
|
||||
if (!event) {
|
||||
return LOG_EMBEDDER_ERROR(kInvalidArguments,
|
||||
"View focus event must not be null.");
|
||||
}
|
||||
// The engine must be running to focus a view.
|
||||
auto embedder_engine = reinterpret_cast<flutter::EmbedderEngine*>(engine);
|
||||
if (!embedder_engine->IsValid()) {
|
||||
return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid.");
|
||||
}
|
||||
|
||||
if (!STRUCT_HAS_MEMBER(event, direction)) {
|
||||
return LOG_EMBEDDER_ERROR(kInvalidArguments,
|
||||
"The event struct has invalid size.");
|
||||
}
|
||||
|
||||
flutter::ViewFocusEvent flutter_event(
|
||||
event->view_id, //
|
||||
static_cast<flutter::ViewFocusState>(event->state),
|
||||
static_cast<flutter::ViewFocusDirection>(event->direction));
|
||||
|
||||
embedder_engine->GetShell().GetPlatformView()->SendViewFocusEvent(
|
||||
flutter_event);
|
||||
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
FLUTTER_EXPORT
|
||||
FlutterEngineResult FlutterEngineDeinitialize(FLUTTER_API_SYMBOL(FlutterEngine)
|
||||
engine) {
|
||||
@ -3657,6 +3708,7 @@ FlutterEngineResult FlutterEngineGetProcAddresses(
|
||||
SET_PROC(SetNextFrameCallback, FlutterEngineSetNextFrameCallback);
|
||||
SET_PROC(AddView, FlutterEngineAddView);
|
||||
SET_PROC(RemoveView, FlutterEngineRemoveView);
|
||||
SET_PROC(SendViewFocusEvent, FlutterEngineSendViewFocusEvent);
|
||||
#undef SET_PROC
|
||||
|
||||
return kSuccess;
|
||||
|
@ -1060,6 +1060,74 @@ typedef struct {
|
||||
FlutterRemoveViewCallback remove_view_callback;
|
||||
} FlutterRemoveViewInfo;
|
||||
|
||||
/// Represents the direction in which the focus transitioned across
|
||||
/// [FlutterView]s.
|
||||
typedef enum {
|
||||
/// Indicates the focus transition did not have a direction.
|
||||
///
|
||||
/// This is typically associated with focus being programmatically requested
|
||||
/// or when focus is lost.
|
||||
kUndefined,
|
||||
|
||||
/// Indicates the focus transition was performed in a forward direction.
|
||||
///
|
||||
/// This is typically result of the user pressing tab.
|
||||
kForward,
|
||||
|
||||
/// Indicates the focus transition was performed in a backward direction.
|
||||
///
|
||||
/// This is typically result of the user pressing shift + tab.
|
||||
kBackward,
|
||||
} FlutterViewFocusDirection;
|
||||
|
||||
/// Represents the focus state of a given [FlutterView].
|
||||
typedef enum {
|
||||
/// Specifies that a view does not have platform focus.
|
||||
kUnfocused,
|
||||
|
||||
/// Specifies that a view has platform focus.
|
||||
kFocused,
|
||||
} FlutterViewFocusState;
|
||||
|
||||
/// A view focus event is sent to the engine by the embedder when a native view
|
||||
/// focus state has changed.
|
||||
///
|
||||
/// Passed through FlutterEngineSendViewFocusEvent.
|
||||
typedef struct {
|
||||
/// The size of this struct.
|
||||
/// Must be sizeof(FlutterViewFocusEvent).
|
||||
size_t struct_size;
|
||||
|
||||
/// The identifier of the view that received the focus event.
|
||||
FlutterViewId view_id;
|
||||
|
||||
/// The focus state of the view.
|
||||
FlutterViewFocusState state;
|
||||
|
||||
/// The direction in which the focus transitioned across [FlutterView]s.
|
||||
FlutterViewFocusDirection direction;
|
||||
} FlutterViewFocusEvent;
|
||||
|
||||
/// A FlutterViewFocusChangeRequest is sent by the engine to the embedder when
|
||||
/// when a FlutterView focus state has changed and native view focus
|
||||
/// needs to be updated.
|
||||
///
|
||||
/// Received in FlutterProjectArgs.view_focus_change_request_callback.
|
||||
typedef struct {
|
||||
/// The size of this struct.
|
||||
/// Must be sizeof(FlutterViewFocusChangeRequest).
|
||||
size_t struct_size;
|
||||
|
||||
/// The identifier of the view that received the focus event.
|
||||
FlutterViewId view_id;
|
||||
|
||||
/// The focus state of the view.
|
||||
FlutterViewFocusState state;
|
||||
|
||||
/// The direction in which the focus transitioned across [FlutterView]s.
|
||||
FlutterViewFocusDirection direction;
|
||||
} FlutterViewFocusChangeRequest;
|
||||
|
||||
/// The phase of the pointer event.
|
||||
typedef enum {
|
||||
kCancel,
|
||||
@ -1644,6 +1712,10 @@ typedef void (*FlutterChannelUpdateCallback)(
|
||||
const FlutterChannelUpdate* /* channel update */,
|
||||
void* /* user data */);
|
||||
|
||||
typedef void (*FlutterViewFocusChangeRequestCallback)(
|
||||
const FlutterViewFocusChangeRequest* /* request */,
|
||||
void* /* user data */);
|
||||
|
||||
typedef struct _FlutterTaskRunner* FlutterTaskRunner;
|
||||
|
||||
typedef struct {
|
||||
@ -2553,6 +2625,12 @@ typedef struct {
|
||||
/// being registered on the framework side. The callback is invoked from
|
||||
/// a task posted to the platform thread.
|
||||
FlutterChannelUpdateCallback channel_update_callback;
|
||||
|
||||
/// The callback invoked by the engine when FlutterView focus state has
|
||||
/// changed. The embedder can use this callback to request focus change for
|
||||
/// the native view. The callback is invoked from a task posted to the
|
||||
/// platform thread.
|
||||
FlutterViewFocusChangeRequestCallback view_focus_change_request_callback;
|
||||
} FlutterProjectArgs;
|
||||
|
||||
#ifndef FLUTTER_ENGINE_NO_PROTOTYPES
|
||||
@ -2757,6 +2835,16 @@ FlutterEngineResult FlutterEngineRemoveView(FLUTTER_API_SYMBOL(FlutterEngine)
|
||||
engine,
|
||||
const FlutterRemoveViewInfo* info);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/// @brief Notifies the engine that platform view focus state has changed.
|
||||
///
|
||||
/// @param[in] engine A running engine instance
|
||||
/// @param[in] event The focus event data describing the change.
|
||||
FLUTTER_EXPORT
|
||||
FlutterEngineResult FlutterEngineSendViewFocusEvent(
|
||||
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
||||
const FlutterViewFocusEvent* event);
|
||||
|
||||
FLUTTER_EXPORT
|
||||
FlutterEngineResult FlutterEngineSendWindowMetricsEvent(
|
||||
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
||||
@ -3433,6 +3521,9 @@ typedef FlutterEngineResult (*FlutterEngineAddViewFnPtr)(
|
||||
typedef FlutterEngineResult (*FlutterEngineRemoveViewFnPtr)(
|
||||
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
||||
const FlutterRemoveViewInfo* info);
|
||||
typedef FlutterEngineResult (*FlutterEngineSendViewFocusEventFnPtr)(
|
||||
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
||||
const FlutterViewFocusEvent* event);
|
||||
|
||||
/// Function-pointer-based versions of the APIs above.
|
||||
typedef struct {
|
||||
@ -3481,6 +3572,7 @@ typedef struct {
|
||||
FlutterEngineSetNextFrameCallbackFnPtr SetNextFrameCallback;
|
||||
FlutterEngineAddViewFnPtr AddView;
|
||||
FlutterEngineRemoveViewFnPtr RemoveView;
|
||||
FlutterEngineSendViewFocusEventFnPtr SendViewFocusEvent;
|
||||
} FlutterEngineProcTable;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
@ -1600,3 +1600,30 @@ Future<void> render_impeller_image_snapshot_test() async {
|
||||
final bool result = (pixel & 0xFF) == color.alpha && ((pixel >> 8) & 0xFF) == color.blue;
|
||||
notifyBoolValue(result);
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void testSendViewFocusEvent() {
|
||||
PlatformDispatcher.instance.onViewFocusChange = (ViewFocusEvent event) {
|
||||
notifyStringValue('${event.viewId} ${event.state} ${event.direction}');
|
||||
};
|
||||
signalNativeTest();
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void testSendViewFocusChangeRequest() {
|
||||
PlatformDispatcher.instance.requestViewFocusChange(
|
||||
viewId: 1,
|
||||
state: ViewFocusState.unfocused,
|
||||
direction: ViewFocusDirection.undefined,
|
||||
);
|
||||
PlatformDispatcher.instance.requestViewFocusChange(
|
||||
viewId: 2,
|
||||
state: ViewFocusState.focused,
|
||||
direction: ViewFocusDirection.forward,
|
||||
);
|
||||
PlatformDispatcher.instance.requestViewFocusChange(
|
||||
viewId: 3,
|
||||
state: ViewFocusState.focused,
|
||||
direction: ViewFocusDirection.backward,
|
||||
);
|
||||
}
|
||||
|
@ -207,6 +207,13 @@ void PlatformViewEmbedder::SendChannelUpdate(const std::string& name,
|
||||
}
|
||||
}
|
||||
|
||||
void PlatformViewEmbedder::RequestViewFocusChange(
|
||||
const ViewFocusChangeRequest& request) {
|
||||
if (platform_dispatch_table_.view_focus_change_request_callback != nullptr) {
|
||||
platform_dispatch_table_.view_focus_change_request_callback(request);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<PlatformMessageHandler>
|
||||
PlatformViewEmbedder::GetPlatformMessageHandler() const {
|
||||
return platform_message_handler_;
|
||||
|
@ -45,6 +45,8 @@ class PlatformViewEmbedder final : public PlatformView {
|
||||
const std::vector<std::string>& supported_locale_data)>;
|
||||
using OnPreEngineRestartCallback = std::function<void()>;
|
||||
using ChanneUpdateCallback = std::function<void(const std::string&, bool)>;
|
||||
using ViewFocusChangeRequestCallback =
|
||||
std::function<void(const ViewFocusChangeRequest&)>;
|
||||
|
||||
struct PlatformDispatchTable {
|
||||
UpdateSemanticsCallback update_semantics_callback; // optional
|
||||
@ -55,6 +57,8 @@ class PlatformViewEmbedder final : public PlatformView {
|
||||
compute_platform_resolved_locale_callback;
|
||||
OnPreEngineRestartCallback on_pre_engine_restart_callback; // optional
|
||||
ChanneUpdateCallback on_channel_update; // optional
|
||||
ViewFocusChangeRequestCallback
|
||||
view_focus_change_request_callback; // optional
|
||||
};
|
||||
|
||||
// Create a platform view that sets up a software rasterizer.
|
||||
@ -142,6 +146,9 @@ class PlatformViewEmbedder final : public PlatformView {
|
||||
// |PlatformView|
|
||||
void SendChannelUpdate(const std::string& name, bool listening) override;
|
||||
|
||||
// |PlatformView|
|
||||
void RequestViewFocusChange(const ViewFocusChangeRequest& request) override;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(PlatformViewEmbedder);
|
||||
};
|
||||
|
||||
|
@ -32,6 +32,10 @@ class MockDelegate : public PlatformView::Delegate {
|
||||
OnPlatformViewRemoveView,
|
||||
(int64_t view_id, RemoveViewCallback callback),
|
||||
(override));
|
||||
MOCK_METHOD(void,
|
||||
OnPlatformViewSendViewFocusEvent,
|
||||
(const ViewFocusEvent& event),
|
||||
(override));
|
||||
MOCK_METHOD(void,
|
||||
OnPlatformViewSetNextFrameCallback,
|
||||
(const fml::closure& closure),
|
||||
|
@ -37,6 +37,7 @@ EmbedderConfigBuilder::EmbedderConfigBuilder(
|
||||
SetLogMessageCallbackHook();
|
||||
SetLocalizationCallbackHooks();
|
||||
SetChannelUpdateCallbackHook();
|
||||
SetViewFocusChangeRequestHook();
|
||||
AddCommandLineArgument("--disable-vm-service");
|
||||
|
||||
if (preference == InitializationPreference::kSnapshotsInitialize ||
|
||||
@ -112,6 +113,11 @@ void EmbedderConfigBuilder::SetChannelUpdateCallbackHook() {
|
||||
context_.GetChannelUpdateCallbackHook();
|
||||
}
|
||||
|
||||
void EmbedderConfigBuilder::SetViewFocusChangeRequestHook() {
|
||||
project_args_.view_focus_change_request_callback =
|
||||
context_.GetViewFocusChangeRequestCallbackHook();
|
||||
}
|
||||
|
||||
void EmbedderConfigBuilder::SetLogTag(std::string tag) {
|
||||
log_tag_ = std::move(tag);
|
||||
project_args_.log_tag = log_tag_.c_str();
|
||||
@ -194,6 +200,11 @@ void EmbedderConfigBuilder::SetPlatformMessageCallback(
|
||||
context_.SetPlatformMessageCallback(callback);
|
||||
}
|
||||
|
||||
void EmbedderConfigBuilder::SetViewFocusChangeRequestCallback(
|
||||
const std::function<void(const FlutterViewFocusChangeRequest*)>& callback) {
|
||||
context_.SetViewFocusChangeRequestCallback(callback);
|
||||
}
|
||||
|
||||
void EmbedderConfigBuilder::SetCompositor(bool avoid_backing_store_cache,
|
||||
bool use_present_layers_callback) {
|
||||
context_.SetupCompositor();
|
||||
|
@ -59,6 +59,8 @@ class EmbedderConfigBuilder {
|
||||
|
||||
void SetChannelUpdateCallbackHook();
|
||||
|
||||
void SetViewFocusChangeRequestHook();
|
||||
|
||||
// Used to set a custom log tag.
|
||||
void SetLogTag(std::string tag);
|
||||
|
||||
@ -81,6 +83,10 @@ class EmbedderConfigBuilder {
|
||||
void SetPlatformMessageCallback(
|
||||
const std::function<void(const FlutterPlatformMessage*)>& callback);
|
||||
|
||||
void SetViewFocusChangeRequestCallback(
|
||||
const std::function<void(const FlutterViewFocusChangeRequest*)>&
|
||||
callback);
|
||||
|
||||
void SetCompositor(bool avoid_backing_store_cache = false,
|
||||
bool use_present_layers_callback = false);
|
||||
|
||||
@ -101,6 +107,9 @@ class EmbedderConfigBuilder {
|
||||
// text context vis `SetVsyncCallback`.
|
||||
void SetupVsyncCallback();
|
||||
|
||||
void SetViewFocusChangeRequestCallback(
|
||||
const FlutterViewFocusChangeRequestCallback& callback);
|
||||
|
||||
private:
|
||||
EmbedderTestContext& context_;
|
||||
FlutterProjectArgs project_args_ = {};
|
||||
|
@ -156,6 +156,11 @@ void EmbedderTestContext::SetChannelUpdateCallback(
|
||||
channel_update_callback_ = callback;
|
||||
}
|
||||
|
||||
void EmbedderTestContext::SetViewFocusChangeRequestCallback(
|
||||
const ViewFocusChangeRequestCallback& callback) {
|
||||
view_focus_change_request_callback_ = callback;
|
||||
}
|
||||
|
||||
void EmbedderTestContext::PlatformMessageCallback(
|
||||
const FlutterPlatformMessage* message) {
|
||||
if (platform_message_callback_) {
|
||||
@ -255,6 +260,16 @@ EmbedderTestContext::GetChannelUpdateCallbackHook() {
|
||||
};
|
||||
}
|
||||
|
||||
FlutterViewFocusChangeRequestCallback
|
||||
EmbedderTestContext::GetViewFocusChangeRequestCallbackHook() {
|
||||
return [](const FlutterViewFocusChangeRequest* request, void* user_data) {
|
||||
auto context = reinterpret_cast<EmbedderTestContext*>(user_data);
|
||||
if (context->view_focus_change_request_callback_) {
|
||||
context->view_focus_change_request_callback_(request);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
FlutterTransformation EmbedderTestContext::GetRootSurfaceTransformation() {
|
||||
return FlutterTransformationMake(root_surface_transformation_);
|
||||
}
|
||||
|
@ -34,6 +34,8 @@ using SemanticsActionCallback =
|
||||
using LogMessageCallback =
|
||||
std::function<void(const char* tag, const char* message)>;
|
||||
using ChannelUpdateCallback = std::function<void(const FlutterChannelUpdate*)>;
|
||||
using ViewFocusChangeRequestCallback =
|
||||
std::function<void(const FlutterViewFocusChangeRequest*)>;
|
||||
|
||||
struct AOTDataDeleter {
|
||||
void operator()(FlutterEngineAOTData aot_data) {
|
||||
@ -94,6 +96,9 @@ class EmbedderTestContext {
|
||||
|
||||
void SetChannelUpdateCallback(const ChannelUpdateCallback& callback);
|
||||
|
||||
void SetViewFocusChangeRequestCallback(
|
||||
const ViewFocusChangeRequestCallback& callback);
|
||||
|
||||
std::future<sk_sp<SkImage>> GetNextSceneImage();
|
||||
|
||||
EmbedderTestCompositor& GetCompositor();
|
||||
@ -133,6 +138,7 @@ class EmbedderTestContext {
|
||||
SemanticsNodeCallback update_semantics_node_callback_;
|
||||
SemanticsActionCallback update_semantics_custom_action_callback_;
|
||||
ChannelUpdateCallback channel_update_callback_;
|
||||
ViewFocusChangeRequestCallback view_focus_change_request_callback_;
|
||||
std::function<void(const FlutterPlatformMessage*)> platform_message_callback_;
|
||||
LogMessageCallback log_message_callback_;
|
||||
std::unique_ptr<EmbedderTestCompositor> compositor_;
|
||||
@ -158,6 +164,8 @@ class EmbedderTestContext {
|
||||
|
||||
FlutterChannelUpdateCallback GetChannelUpdateCallbackHook();
|
||||
|
||||
FlutterViewFocusChangeRequestCallback GetViewFocusChangeRequestCallbackHook();
|
||||
|
||||
void SetupAOTMappingsIfNecessary();
|
||||
|
||||
void SetupAOTDataIfNecessary();
|
||||
|
@ -2045,6 +2045,124 @@ TEST_F(EmbedderTest, CanRenderMultipleViews) {
|
||||
latch123.Wait();
|
||||
}
|
||||
|
||||
bool operator==(const FlutterViewFocusChangeRequest& lhs,
|
||||
const FlutterViewFocusChangeRequest& rhs) {
|
||||
return lhs.view_id == rhs.view_id && lhs.state == rhs.state &&
|
||||
lhs.direction == rhs.direction;
|
||||
}
|
||||
|
||||
TEST_F(EmbedderTest, SendsViewFocusChangeRequest) {
|
||||
auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
|
||||
auto platform_task_runner = CreateNewThread("test_platform_thread");
|
||||
UniqueEngine engine;
|
||||
static std::mutex engine_mutex;
|
||||
EmbedderTestTaskRunner test_platform_task_runner(
|
||||
platform_task_runner, [&](FlutterTask task) {
|
||||
std::scoped_lock lock(engine_mutex);
|
||||
if (!engine.is_valid()) {
|
||||
return;
|
||||
}
|
||||
FlutterEngineRunTask(engine.get(), &task);
|
||||
});
|
||||
fml::CountDownLatch latch(3);
|
||||
std::vector<FlutterViewFocusChangeRequest> received_requests;
|
||||
platform_task_runner->PostTask([&]() {
|
||||
EmbedderConfigBuilder builder(context);
|
||||
builder.SetSurface(SkISize::Make(1, 1));
|
||||
builder.SetDartEntrypoint("testSendViewFocusChangeRequest");
|
||||
const auto platform_task_runner_description =
|
||||
test_platform_task_runner.GetFlutterTaskRunnerDescription();
|
||||
builder.SetPlatformTaskRunner(&platform_task_runner_description);
|
||||
builder.SetViewFocusChangeRequestCallback(
|
||||
[&](const FlutterViewFocusChangeRequest* request) {
|
||||
EXPECT_TRUE(platform_task_runner->RunsTasksOnCurrentThread());
|
||||
received_requests.push_back(*request);
|
||||
latch.CountDown();
|
||||
});
|
||||
engine = builder.LaunchEngine();
|
||||
ASSERT_TRUE(engine.is_valid());
|
||||
});
|
||||
latch.Wait();
|
||||
|
||||
std::vector<FlutterViewFocusChangeRequest> expected_requests{
|
||||
{.view_id = 1, .state = kUnfocused, .direction = kUndefined},
|
||||
{.view_id = 2, .state = kFocused, .direction = kForward},
|
||||
{.view_id = 3, .state = kFocused, .direction = kBackward},
|
||||
};
|
||||
|
||||
ASSERT_EQ(received_requests.size(), expected_requests.size());
|
||||
for (size_t i = 0; i < received_requests.size(); ++i) {
|
||||
ASSERT_TRUE(received_requests[i] == expected_requests[i]);
|
||||
}
|
||||
|
||||
fml::AutoResetWaitableEvent kill_latch;
|
||||
platform_task_runner->PostTask(fml::MakeCopyable([&]() mutable {
|
||||
std::scoped_lock lock(engine_mutex);
|
||||
engine.reset();
|
||||
|
||||
// There may still be pending tasks on the platform thread that were queued
|
||||
// by the test_task_runner. Signal the latch after these tasks have been
|
||||
// consumed.
|
||||
platform_task_runner->PostTask([&kill_latch] { kill_latch.Signal(); });
|
||||
}));
|
||||
kill_latch.Wait();
|
||||
}
|
||||
|
||||
TEST_F(EmbedderTest, CanSendViewFocusEvent) {
|
||||
auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
|
||||
EmbedderConfigBuilder builder(context);
|
||||
builder.SetSurface(SkISize::Make(1, 1));
|
||||
builder.SetDartEntrypoint("testSendViewFocusEvent");
|
||||
|
||||
fml::AutoResetWaitableEvent latch;
|
||||
std::string last_event;
|
||||
|
||||
context.AddNativeCallback(
|
||||
"SignalNativeTest",
|
||||
CREATE_NATIVE_ENTRY(
|
||||
[&latch](Dart_NativeArguments args) { latch.Signal(); }));
|
||||
context.AddNativeCallback("NotifyStringValue",
|
||||
CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
|
||||
const auto message_from_dart =
|
||||
tonic::DartConverter<std::string>::FromDart(
|
||||
Dart_GetNativeArgument(args, 0));
|
||||
last_event = message_from_dart;
|
||||
latch.Signal();
|
||||
}));
|
||||
|
||||
auto engine = builder.LaunchEngine();
|
||||
ASSERT_TRUE(engine.is_valid());
|
||||
// Wait until the focus change handler is attached.
|
||||
latch.Wait();
|
||||
latch.Reset();
|
||||
|
||||
FlutterViewFocusEvent event1{
|
||||
.struct_size = sizeof(FlutterViewFocusEvent),
|
||||
.view_id = 1,
|
||||
.state = kFocused,
|
||||
.direction = kUndefined,
|
||||
};
|
||||
FlutterEngineResult result =
|
||||
FlutterEngineSendViewFocusEvent(engine.get(), &event1);
|
||||
ASSERT_EQ(result, kSuccess);
|
||||
latch.Wait();
|
||||
ASSERT_EQ(last_event,
|
||||
"1 ViewFocusState.focused ViewFocusDirection.undefined");
|
||||
|
||||
FlutterViewFocusEvent event2{
|
||||
.struct_size = sizeof(FlutterViewFocusEvent),
|
||||
.view_id = 2,
|
||||
.state = kUnfocused,
|
||||
.direction = kBackward,
|
||||
};
|
||||
latch.Reset();
|
||||
result = FlutterEngineSendViewFocusEvent(engine.get(), &event2);
|
||||
ASSERT_EQ(result, kSuccess);
|
||||
latch.Wait();
|
||||
ASSERT_EQ(last_event,
|
||||
"2 ViewFocusState.unfocused ViewFocusDirection.backward");
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/// Test that the backing store is created with the correct view ID, is used
|
||||
/// for the correct view, and is cached according to their views.
|
||||
|
@ -157,6 +157,9 @@ class MockPlatformViewDelegate : public flutter::PlatformView::Delegate {
|
||||
void UpdateAssetResolverByType(
|
||||
std::unique_ptr<flutter::AssetResolver> updated_asset_resolver,
|
||||
flutter::AssetResolver::AssetResolverType type) {}
|
||||
// |flutter::PlatformView::Delegate|
|
||||
void OnPlatformViewSendViewFocusEvent(const flutter::ViewFocusEvent& event) {
|
||||
};
|
||||
|
||||
flutter::Surface* surface() const { return surface_.get(); }
|
||||
flutter::PlatformMessage* message() const { return message_.get(); }
|
||||
|
Loading…
x
Reference in New Issue
Block a user