Add support for system text scale factor. (#4124)

Adds support for system text scale factor, including hooks for Android system settings changes.  iOS hooks will be added in another PR.
This commit is contained in:
gspencergoog 2017-09-29 13:19:06 -07:00 committed by GitHub
parent b930f107c3
commit b2a7f4bf8f
11 changed files with 168 additions and 14 deletions

View File

@ -32,6 +32,11 @@ void _updateLocale(String languageCode, String countryCode) {
_invoke(window.onLocaleChanged, window._onLocaleChangedZone);
}
void _updateTextScaleFactor(double textScaleFactor) {
window._textScaleFactor = textScaleFactor;
_invoke(window.onTextScaleFactorChanged, window._onTextScaleFactorChangedZone);
}
void _updateSemanticsEnabled(bool enabled) {
window._semanticsEnabled = enabled;
_invoke(window.onSemanticsEnabledChanged, window._onSemanticsEnabledChangedZone);

View File

@ -254,6 +254,38 @@ class Window {
_onLocaleChangedZone = Zone.current;
}
/// The system-reported text scale.
///
/// This establishes the text scaling factor to use when rendering text,
/// according to the user's platform preferences.
///
/// The [onTextScaleFactorChanged] callback is called whenever this value
/// changes.
///
/// See also:
///
/// * [WidgetsBindingObserver], for a mechanism at the widgets layer to
/// observe when this value changes.
double get textScaleFactor => _textScaleFactor;
double _textScaleFactor = 1.0;
/// A callback that is invoked whenever [textScaleFactor] changes value.
///
/// The framework invokes this callback in the same zone in which the
/// callback was set.
///
/// See also:
///
/// * [WidgetsBindingObserver], for a mechanism at the widgets layer to
/// observe when this callback is invoked.
VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
VoidCallback _onTextScaleFactorChanged;
Zone _onTextScaleFactorChangedZone;
set onTextScaleFactorChanged(VoidCallback callback) {
_onTextScaleFactorChanged = callback;
_onTextScaleFactorChangedZone = Zone.current;
}
/// A callback that is invoked to notify the application that it is an
/// appropriate time to provide a scene using the [SceneBuilder] API and the
/// [render] method. When possible, this is driven by the hardware VSync

View File

@ -163,6 +163,18 @@ void Window::UpdateLocale(const std::string& language_code,
});
}
void Window::UpdateTextScaleFactor(double text_scale_factor) {
tonic::DartState* dart_state = library_.dart_state().get();
if (!dart_state)
return;
tonic::DartState::Scope scope(dart_state);
DartInvokeField(library_.value(), "_updateTextScaleFactor",
{
ToDart(static_cast<double>(text_scale_factor)),
});
}
void Window::UpdateSemanticsEnabled(bool enabled) {
tonic::DartState* dart_state = library_.dart_state().get();
if (!dart_state)

View File

@ -45,6 +45,7 @@ class Window {
void UpdateWindowMetrics(const ViewportMetrics& metrics);
void UpdateLocale(const std::string& language_code,
const std::string& country_code);
void UpdateTextScaleFactor(double text_scale_factor);
void UpdateSemanticsEnabled(bool enabled);
void DispatchPlatformMessage(fxl::RefPtr<PlatformMessage> message);
void DispatchPointerDataPacket(const PointerDataPacket& packet);

View File

@ -66,6 +66,13 @@ void RuntimeController::SetLocale(const std::string& language_code,
GetWindow()->UpdateLocale(language_code_, country_code_);
}
void RuntimeController::SetTextScaleFactor(double text_scale_factor) {
if (text_scale_factor_ == text_scale_factor)
return;
text_scale_factor_ = text_scale_factor;
GetWindow()->UpdateTextScaleFactor(text_scale_factor_);
}
void RuntimeController::SetSemanticsEnabled(bool enabled) {
if (semantics_enabled_ == enabled)
return;

View File

@ -35,6 +35,7 @@ class RuntimeController : public WindowClient, public IsolateClient {
void SetViewportMetrics(const ViewportMetrics& metrics);
void SetLocale(const std::string& language_code,
const std::string& country_code);
void SetTextScaleFactor(double textScaleFactor);
void SetSemanticsEnabled(bool enabled);
void BeginFrame(fxl::TimePoint frame_time);
@ -66,6 +67,7 @@ class RuntimeController : public WindowClient, public IsolateClient {
RuntimeDelegate* client_;
std::string language_code_;
std::string country_code_;
double text_scale_factor_ = 1.0;
bool semantics_enabled_ = false;
std::unique_ptr<DartController> dart_controller_;

View File

@ -44,6 +44,7 @@ constexpr char kAssetChannel[] = "flutter/assets";
constexpr char kLifecycleChannel[] = "flutter/lifecycle";
constexpr char kNavigationChannel[] = "flutter/navigation";
constexpr char kLocalizationChannel[] = "flutter/localization";
constexpr char kSystemChannel[] = "flutter/system";
bool PathExists(const std::string& path) {
return access(path.c_str(), R_OK) == 0;
@ -74,6 +75,7 @@ Engine::Engine(PlatformView* platform_view)
platform_view->GetVsyncWaiter(),
this)),
load_script_error_(tonic::kNoError),
text_scale_factor_(1.0),
activity_running_(false),
have_surface_(false),
weak_factory_(this) {}
@ -312,7 +314,12 @@ void Engine::DispatchPlatformMessage(
if (HandleLifecyclePlatformMessage(message.get()))
return;
} else if (message->channel() == kLocalizationChannel) {
if (HandleLocalizationPlatformMessage(std::move(message)))
if (HandleLocalizationPlatformMessage(message.get()))
return;
} else if (message->channel() == kSystemChannel) {
// This only handles textScaleFactor changes: other system messages are
// handled by DispatchPlatformMessage below.
if (HandleSystemPlatformMessage(message.get()))
return;
}
@ -367,7 +374,7 @@ bool Engine::HandleNavigationPlatformMessage(
}
bool Engine::HandleLocalizationPlatformMessage(
fxl::RefPtr<blink::PlatformMessage> message) {
blink::PlatformMessage* message) {
const auto& data = message->data();
rapidjson::Document document;
@ -396,6 +403,37 @@ bool Engine::HandleLocalizationPlatformMessage(
return true;
}
bool Engine::HandleSystemPlatformMessage(blink::PlatformMessage* message) {
const auto& data = message->data();
rapidjson::Document document;
document.Parse(reinterpret_cast<const char*>(data.data()), data.size());
if (document.HasParseError() || !document.IsObject())
return false;
auto root = document.GetObject();
auto type = root.FindMember("type");
if (type == root.MemberEnd() || type->value != "systemSettings")
return false;
// This only handles textScaleFactor changes: other system messages
// are handled by DispatchPlatformMessage.
auto text_scale_factor = root.FindMember("textScaleFactor");
if (text_scale_factor == root.MemberEnd() ||
!text_scale_factor->value.IsDouble()) {
return false;
}
text_scale_factor_ = text_scale_factor->value.GetDouble();
if (runtime_) {
runtime_->SetTextScaleFactor(text_scale_factor_);
if (have_surface_)
ScheduleFrame();
}
// If the only members were "type" and "textScaleFactor", then we're done.
// If there are more members, then we need to send it on to other handlers.
return root.MemberCount() == 2;
}
void Engine::DispatchPointerDataPacket(const PointerDataPacket& packet) {
if (runtime_)
runtime_->DispatchPointerDataPacket(packet);
@ -445,6 +483,7 @@ void Engine::ConfigureRuntime(const std::string& script_uri,
default_isolate_snapshot_instr, platform_kernel);
runtime_->SetViewportMetrics(viewport_metrics_);
runtime_->SetLocale(language_code_, country_code_);
runtime_->SetTextScaleFactor(text_scale_factor_);
runtime_->SetSemanticsEnabled(semantics_enabled_);
}

View File

@ -92,8 +92,8 @@ class Engine : public blink::RuntimeDelegate {
bool HandleLifecyclePlatformMessage(blink::PlatformMessage* message);
bool HandleNavigationPlatformMessage(
fxl::RefPtr<blink::PlatformMessage> message);
bool HandleLocalizationPlatformMessage(
fxl::RefPtr<blink::PlatformMessage> message);
bool HandleLocalizationPlatformMessage(blink::PlatformMessage* message);
bool HandleSystemPlatformMessage(blink::PlatformMessage* message);
void HandleAssetPlatformMessage(fxl::RefPtr<blink::PlatformMessage> message);
bool GetAssetAsBuffer(const std::string& name, std::vector<uint8_t>* data);
@ -106,6 +106,7 @@ class Engine : public blink::RuntimeDelegate {
blink::ViewportMetrics viewport_metrics_;
std::string language_code_;
std::string country_code_;
double text_scale_factor_;
bool semantics_enabled_ = false;
// TODO(abarth): Unify these two behind a common interface.
fxl::RefPtr<blink::ZipAssetStore> asset_store_;

View File

@ -11,7 +11,7 @@
<uses-feature android:name="android.hardware.sensor.accelerometer" android:required="true" />
<application android:label="Flutter Shell" android:name="FlutterApplication" android:debuggable="true">
<activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
<activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale"
android:hardwareAccelerated="true"
android:launchMode="standard"
android:name="FlutterActivity"

View File

@ -181,6 +181,7 @@ public class FlutterView extends SurfaceView
mTextInputPlugin = new TextInputPlugin(this);
setLocale(getResources().getConfiguration().locale);
setTextScaleFactor(getResources().getConfiguration().fontScale);
if ((context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
mDiscoveryReceiver = new DiscoveryReceiver();
@ -278,6 +279,13 @@ public class FlutterView extends SurfaceView
mFlutterNavigationChannel.invokeMethod("popRoute", null);
}
private void setTextScaleFactor(float textScaleFactor) {
Map<String, Object> message = new HashMap<>();
message.put("type", "systemSettings");
message.put("textScaleFactor", textScaleFactor);
mFlutterSystemChannel.send(message);
}
private void setLocale(Locale locale) {
mFlutterLocalizationChannel.invokeMethod("setLocale",
Arrays.asList(locale.getLanguage(), locale.getCountry()));
@ -287,6 +295,7 @@ public class FlutterView extends SurfaceView
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setLocale(newConfig.locale);
setTextScaleFactor(newConfig.fontScale);
}
float getDevicePixelRatio() {

View File

@ -34,6 +34,7 @@ void main() {
VoidCallback originalOnSemanticsEnabledChanged;
SemanticsActionCallback originalOnSemanticsAction;
PlatformMessageCallback originalOnPlatformMessage;
VoidCallback originalOnTextScaleFactorChanged;
setUp(() {
originalOnMetricsChanged = window.onMetricsChanged;
@ -44,6 +45,7 @@ void main() {
originalOnSemanticsEnabledChanged = window.onSemanticsEnabledChanged;
originalOnSemanticsAction = window.onSemanticsAction;
originalOnPlatformMessage = window.onPlatformMessage;
originalOnTextScaleFactorChanged = window.onTextScaleFactorChanged;
});
tearDown(() {
@ -55,55 +57,65 @@ void main() {
window.onSemanticsEnabledChanged = originalOnSemanticsEnabledChanged;
window.onSemanticsAction = originalOnSemanticsAction;
window.onPlatformMessage = originalOnPlatformMessage;
window.onTextScaleFactorChanged = originalOnTextScaleFactorChanged;
});
test('onMetricsChanged preserves callback zone', () {
Zone innerZone;
Zone runZone;
double devicePixelRatio;
runZoned(() {
innerZone = Zone.current;
window.onMetricsChanged = () {
runZone = Zone.current;
devicePixelRatio = window.devicePixelRatio;
};
});
window.onMetricsChanged();
_updateWindowMetrics(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
_updateWindowMetrics(0.1234, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
expect(runZone, isNotNull);
expect(runZone, same(innerZone));
expect(devicePixelRatio, equals(0.1234));
});
test('onLocaleChanged preserves callback zone', () {
Zone innerZone;
Zone runZone;
Locale locale;
runZoned(() {
innerZone = Zone.current;
window.onLocaleChanged = () {
runZone = Zone.current;
locale = window.locale;
};
});
_updateLocale('en', 'US');
expect(runZone, isNotNull);
expect(runZone, same(innerZone));
expect(locale, equals(const Locale('en', 'US')));
});
test('onBeginFrame preserves callback zone', () {
Zone innerZone;
Zone runZone;
Duration start;
runZoned(() {
innerZone = Zone.current;
window.onBeginFrame = (_) {
window.onBeginFrame = (Duration value) {
runZone = Zone.current;
start = value;
};
});
_beginFrame(0);
_beginFrame(1234);
expect(runZone, isNotNull);
expect(runZone, same(innerZone));
expect(start, equals(const Duration(microseconds: 1234)));
});
test('onDrawFrame preserves callback zone', () {
@ -125,65 +137,99 @@ void main() {
test('onPointerDataPacket preserves callback zone', () {
Zone innerZone;
Zone runZone;
PointerDataPacket data;
runZoned(() {
innerZone = Zone.current;
window.onPointerDataPacket = (_) {
window.onPointerDataPacket = (PointerDataPacket value) {
runZone = Zone.current;
data = value;
};
});
_dispatchPointerDataPacket(new ByteData.view(new Uint8List(0).buffer));
final ByteData testData = new ByteData.view(new Uint8List(0).buffer);
_dispatchPointerDataPacket(testData);
expect(runZone, isNotNull);
expect(runZone, same(innerZone));
expect(data.data, equals(_unpackPointerDataPacket(testData).data));
});
test('onSemanticsEnabledChanged preserves callback zone', () {
Zone innerZone;
Zone runZone;
bool enabled;
runZoned(() {
innerZone = Zone.current;
window.onSemanticsEnabledChanged = () {
runZone = Zone.current;
enabled = window.semanticsEnabled;
};
});
_updateSemanticsEnabled(window._semanticsEnabled);
expect(runZone, isNotNull);
expect(runZone, same(innerZone));
expect(enabled, isNotNull);
expect(enabled, equals(window._semanticsEnabled));
});
test('onSemanticsAction preserves callback zone', () {
Zone innerZone;
Zone runZone;
int action;
runZoned(() {
innerZone = Zone.current;
window.onSemanticsAction = (_, __) {
window.onSemanticsAction = (int value, _) {
runZone = Zone.current;
action = value;
};
});
_dispatchSemanticsAction(0, 0);
_dispatchSemanticsAction(1234, 0);
expect(runZone, isNotNull);
expect(runZone, same(innerZone));
expect(action, equals(1234));
});
test('onPlatformMessage preserves callback zone', () {
Zone innerZone;
Zone runZone;
String name;
runZoned(() {
innerZone = Zone.current;
window.onPlatformMessage = (_, __, ___) {
window.onPlatformMessage = (String value, _, __) {
runZone = Zone.current;
name = value;
};
});
_dispatchPlatformMessage(null, null, null);
_dispatchPlatformMessage('testName', null, null);
expect(runZone, isNotNull);
expect(runZone, same(innerZone));
expect(name, equals('testName'));
});
test('onTextScaleFactorChanged preserves callback zone', () {
Zone innerZone;
Zone runZone;
double textScaleFactor;
runZoned(() {
innerZone = Zone.current;
window.onTextScaleFactorChanged = () {
runZone = Zone.current;
textScaleFactor = window.textScaleFactor;
};
});
window.onTextScaleFactorChanged();
_updateTextScaleFactor(0.5);
expect(runZone, isNotNull);
expect(runZone, same(innerZone));
expect(textScaleFactor, equals(0.5));
});
});
}