Skip to content

Implement system fonts system channel listener #38930

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 24, 2019

Conversation

chunhtai
Copy link
Contributor

@chunhtai chunhtai commented Aug 20, 2019

Description

This pr add a system channel listener to repaint RenderParagraph and RenderEditable upon receiving system fonts change notification. We will have engine side change coming soon

flutter/engine#12276

Related Issues

#38556

Tests

I added the following tests:

RenderParagraph relayout upon system fonts changes
RenderEditable relayout upon system fonts changes

Checklist

Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes ([x]). This will ensure a smooth and quick review process.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I signed the CLA.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I updated/added relevant documentation (doc comments with ///).
  • All existing and new tests are passing.
  • The analyzer (flutter analyze --flutter-repo) does not report any problems on my PR.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Does your PR require Flutter developers to manually update their apps to accommodate your change?

  • Yes, this is a breaking change (Please read Handling breaking changes). Replace this with a link to the e-mail where you asked for input on this proposed change.
  • No, this is not a breaking change.

@fluttergithubbot fluttergithubbot added the framework flutter/packages/flutter repository. See also f: labels. label Aug 20, 2019
@chunhtai chunhtai requested a review from goderbauer August 21, 2019 20:09
Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about other objects/widgets that use TextPainter to measure text (example: CupertinoDataPicker or Material's TimePicker)? Do we not have to notify them as well so they can rebuild?

@@ -148,6 +149,21 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
renderView.scheduleInitialFrame();
}

Set<SystemFontsClient> _systemFontsObserver;

/// Handler function that is called when the operating system notifies the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is about painting text, PaintingBinding is probably a better place for this.

@@ -148,6 +149,21 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
renderView.scheduleInitialFrame();
}

Set<SystemFontsClient> _systemFontsObserver;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There probably needs to be a public way to register listeners. Not all listeners are going to be RenderObjects (e.g. all the the non-RenderObject that use TextPainter, which are for example Widgets using CustomPaint like the TimePicker).

@override
void attach(covariant Object owner) {
super.attach(owner);
RendererBinding.instance._systemFontsObserver.add(this);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we just pass in markNeedsLayout directly here? That way, interested RenderObjects would just have to implement the mixin and are good to go. (It also avoids an extra function call when this gets called).

@@ -556,6 +556,9 @@ mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererB
case 'memoryPressure':
handleMemoryPressure();
break;
case 'fontsChange':
handleSystemFontsChange();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is odd that the message bubbles up all the way to the widget layer and is then redirected back to the rendering layer. What if you've implemented your own Widget system based on Flutter's rendering layer? You still want the font thing to work.


@override
void bringIntoView(TextPosition position) { }
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: end with blank line.

@chunhtai
Copy link
Contributor Author

Ready for re review @goderbauer

@@ -246,7 +246,7 @@ abstract class WidgetsBindingObserver {
}

/// The glue between the widgets layer and the Flutter engine.
mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to include this to get passed doc parsing.

/// Client for listening to system fonts change.
///
/// Using this mixin will cause it automatically subscribe to system channel.
mixin SystemFontsClient on RenderObject {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe call this "RelayoutWhenSystemFontsChangeMixin" to better describe what it does?

@@ -415,3 +415,20 @@ class RenderingFlutterBinding extends BindingBase with GestureBinding, ServicesB
renderView.child = root;
}
}

/// Client for listening to system fonts change.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Mixin for [RenderObject] that will call [markNeedsLayout] whenever the system fonts change".

Can you also add an explanation why/when a developer should mix this in?

@override
void attach(covariant Object owner) {
super.attach(owner);
PaintingBinding.instance?.systemFonts?.addListener(markNeedsLayout);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would instance or systemFonts be null? Shouldn't they always be initialized when you get to this place?

@override
void detach() {
super.detach();
PaintingBinding.instance?.systemFonts?.removeListener(markNeedsLayout);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

/// Client for listening to system fonts change.
///
/// Using this mixin will cause it automatically subscribe to system channel.
mixin SystemFontsClient on RenderObject {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably find a better location then rendering/binding.dart for this.

}
}

class _SystemFonts extends Listenable {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe _SystemFontsListener

@@ -100,6 +100,18 @@ class _DatePickerLayoutDelegate extends MultiChildLayoutDelegate {
}
}

/// Retrieves cached widths of columns in the input state.
@visibleForTesting
Map<int, double> debugGetCupertinoDatePickerStateCache(State<CupertinoDatePicker> state) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awkward code to add to the framework. Can we find another way to test this?

}

void _handleSystemFontsChange () {
didChangeDependencies();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calling didChangeDependecies seems awkward (especially because that one calls super). Maybe factor out what you need from didChangeDependencies into a seperate method that we can call fro both places?

}

void _handleSystemFontsChange() {
didChangeDependencies();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

@@ -47,6 +49,11 @@ mixin ServicesBinding on BindingBase {
return const _DefaultBinaryMessenger._();
}

/// Handler for flutter/system message channel.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handler called for messages received on the [SystemChannels.system] call.

Other bindings may override this to respond to incoming system message.

}

void _handleSystemFontsChange () {
estimatedColumnWidths.clear();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put this statement into the setState closure? (That's the state getting changed here, right?)

void _handleSystemFontsChange () {
estimatedColumnWidths.clear();
setState(() {
// didChangeDependencies should update the width if necessary.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment seems outdated?

}

void _handleSystemFontsChange() {
_refreshEstimatedColumnWidths();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put this inside the setState since that's causing the state to change?

void _handleSystemFontsChange() {
_refreshEstimatedColumnWidths();
setState(() {
// didChangeDependencies should update the width if necessary.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment seems outdated?

}

void _handleSystemFontsChange () {
estimatedColumnWidths.clear();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a comment why we have to clear this on system font changes (e.g. because it was determined by measuring the width of text which may have changed with the new fonts).

/// system fonts change
///
/// System fonts can change when OS install or remove a font. Use this mixin if
/// the [RenderObject] uses [TextPainter] or text related painter to correctly
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's a text related painter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using ui.Paragraph directly without going through text painter. I should update the doc comment

@@ -13,6 +13,7 @@ import 'package:flutter/services.dart';

import 'package:vector_math/vector_math_64.dart';

import 'binding.dart';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this import be removed?

@@ -47,6 +49,13 @@ mixin ServicesBinding on BindingBase {
return const _DefaultBinaryMessenger._();
}

/// Handler called for messages received on the [SystemChannels.system] call.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

call -> message channel.

@@ -47,6 +49,13 @@ mixin ServicesBinding on BindingBase {
return const _DefaultBinaryMessenger._();
}

/// Handler called for messages received on the [SystemChannels.system] call.
///
/// Other bindings may override this to respond to incoming system message.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

message -> messages.

case 'memoryPressure':
handleMemoryPressure();
break;
if (type == 'memoryPressure') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the change from a switch?

@googlebot
Copy link

We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors. If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google.
In order to pass this check, please resolve this problem and then comment @googlebot I fixed it.. If the bot doesn't comment, it means it doesn't think anything has changed.

ℹ️ Googlers: Go here for more info.

@googlebot
Copy link

CLAs look good, thanks!

ℹ️ Googlers: Go here for more info.

@chunhtai
Copy link
Contributor Author

@goderbauer this is ready to review.

@override
void systemFontsDidChange() {
super.systemFontsDidChange();
_textPainter.markNeedsLayout();
Copy link
Contributor Author

@chunhtai chunhtai Sep 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure how do we test _textPainter has been marked needs layout. this also apply to all the other widget.
The current test only verify renderobject is dirty after font change. unless I added a bunch of visibleforTesting for all the widgets that have textpainter

void _handleSystemFontsChange () {
setState(() {
// System fonts change might cause the text layout width to change.
// Clears cached widths to make sure we do not have updated values.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do not have updated values? Isn't clearing the cache causing the values to get recalculated/updated?

Maybe:

"Clear cached width to ensure that they get recalculated with the new system fonts."

void _handleSystemFontsChange() {
setState(() {
// System fonts change might cause the text layout width to change.
// Clears cached widths to make sure we do not have updated values.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is not really clearing a cache? It's proactively recalculating the widths?

(You could probably just remove the second line.)

@@ -79,6 +79,49 @@ mixin PaintingBinding on BindingBase, ServicesBinding {
super.evict(asset);
imageCache.clear();
}

/// Listenable for OSes system fonts change event.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Listenable for OSes system fonts change event.
/// Listenable that notifies when the fonts available on the system have changed.

final String type = message['type'];
switch (type) {
case 'fontsChange':
final _SystemFontsNotifier notifier = systemFonts;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this slightly awkward cast you could have a correctly typed private _systemFonts variable and a public systemFonts getter for it.

@@ -161,6 +161,13 @@ class TextPainter {
ui.Paragraph _paragraph;
bool _needsLayout = true;

/// Marks this text painter's layout information as dirty and removes cached
/// information.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should include information about when somebody may want to call this.

/// text when it happens.
mixin RelayoutWhenSystemFontsChangeMixin on RenderObject {

/// A callback that is called when system fonts has changed.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Called when system fonts have changed.


/// A callback that is called when system fonts has changed.
///
/// Subclass should override this method to clear any extra cache.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subclasses should override this method to clear any cached values that depend on font-related metrics.

mixin RelayoutWhenSystemFontsChangeMixin on RenderObject {

/// A callback that is called when system fonts has changed.
///
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default, [mark needsLayout] is called on the [RenderObject] implementing this mixin.

@override
void systemFontsDidChange() {
super.systemFontsDidChange();
_labelPainter.markNeedsLayout();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will _updateLabelPainter not automatically cause this to happen?

@override
void systemFontsDidChange() {
super.systemFontsDidChange();
_startLabelPainter.markNeedsLayout();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will _updateLabelPainters not cause this to happen?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, _updateLabelPainters will set the same property of existing textpainter, which will all get shortcircuited.

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM


/// A callback that is called when system fonts have changed.
///
/// By default, [mark needsLayout] is called on the [RenderObject]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: no space after mark. (sorry...)

@chunhtai chunhtai force-pushed the issues/38556 branch 4 times, most recently from 974dfa6 to 7b41a5f Compare September 23, 2019 18:27
@chunhtai chunhtai merged commit 0ca5e71 into flutter:master Sep 24, 2019
Inconnu08 pushed a commit to Inconnu08/flutter that referenced this pull request Sep 30, 2019
* Implement system fonts system channel listener
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 4, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants