Przeglądaj źródła

Merge pull request #138 from LucasAschenbach/text-scaling

Fix: terminal font size not respecting system level font scale #97
xuty 3 lat temu
rodzic
commit
d5efa84637

+ 13 - 0
lib/src/terminal_view.dart

@@ -27,6 +27,7 @@ class TerminalView extends StatefulWidget {
     this.controller,
     this.theme = TerminalThemes.defaultTheme,
     this.textStyle = const TerminalStyle(),
+    this.textScaleFactor,
     this.padding,
     this.scrollController,
     this.autoResize = true,
@@ -58,6 +59,11 @@ class TerminalView extends StatefulWidget {
   /// The style to use for painting characters.
   final TerminalStyle textStyle;
 
+  /// The number of font pixels for each logical pixel. If null, will use the
+  /// [MediaQueryData.textScaleFactor] obtained from [MediaQuery], or 1.0 if
+  /// there is no [MediaQuery] in scope.
+  final double? textScaleFactor;
+
   /// Padding around the inner [Scrollable] widget.
   final EdgeInsets? padding;
 
@@ -213,6 +219,8 @@ class TerminalViewState extends State<TerminalView> {
           padding: MediaQuery.of(context).padding,
           autoResize: widget.autoResize,
           textStyle: widget.textStyle,
+          textScaleFactor:
+              widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
           theme: widget.theme,
           focusNode: _focusNode,
           cursorType: widget.cursorType,
@@ -409,6 +417,7 @@ class _TerminalView extends LeafRenderObjectWidget {
     required this.padding,
     required this.autoResize,
     required this.textStyle,
+    required this.textScaleFactor,
     required this.theme,
     required this.focusNode,
     required this.cursorType,
@@ -429,6 +438,8 @@ class _TerminalView extends LeafRenderObjectWidget {
 
   final TerminalStyle textStyle;
 
+  final double textScaleFactor;
+
   final TerminalTheme theme;
 
   final FocusNode focusNode;
@@ -450,6 +461,7 @@ class _TerminalView extends LeafRenderObjectWidget {
       padding: padding,
       autoResize: autoResize,
       textStyle: textStyle,
+      textScaleFactor: textScaleFactor,
       theme: theme,
       focusNode: focusNode,
       cursorType: cursorType,
@@ -468,6 +480,7 @@ class _TerminalView extends LeafRenderObjectWidget {
       ..padding = padding
       ..autoResize = autoResize
       ..textStyle = textStyle
+      ..textScaleFactor = textScaleFactor
       ..theme = theme
       ..focusNode = focusNode
       ..cursorType = cursorType

+ 2 - 2
lib/src/ui/char_metrics.dart

@@ -2,12 +2,12 @@ import 'dart:ui';
 
 import 'package:xterm/src/ui/terminal_text_style.dart';
 
-Size calcCharSize(TerminalStyle style) {
+Size calcCharSize(TerminalStyle style, double textScaleFactor) {
   const test = 'mmmmmmmmmm';
 
   final textStyle = style.toTextStyle();
   final builder = ParagraphBuilder(textStyle.getParagraphStyle());
-  builder.pushStyle(textStyle.getTextStyle());
+  builder.pushStyle(textStyle.getTextStyle(textScaleFactor: textScaleFactor));
   builder.addText(test);
 
   final paragraph = builder.build();

+ 18 - 2
lib/src/ui/paragraph_cache.dart

@@ -3,19 +3,31 @@ import 'dart:ui';
 import 'package:flutter/widgets.dart';
 import 'package:quiver/collection.dart';
 
+/// A cache of laid out [Paragraph]s. This is used to avoid laying out the same
+/// text multiple times, which is expensive.
 class ParagraphCache {
   ParagraphCache(int maximumSize)
       : _cache = LruMap<int, Paragraph>(maximumSize: maximumSize);
 
   final LruMap<int, Paragraph> _cache;
 
+  /// Returns a [Paragraph] for the given [key]. [key] is the same as the
+  /// key argument to [performAndCacheLayout].
   Paragraph? getLayoutFromCache(int key) {
     return _cache[key];
   }
 
-  Paragraph performAndCacheLayout(String text, TextStyle style, int key) {
+  /// Applies [style] and [textScaleFactor] to [text] and lays it out to create
+  /// a [Paragraph]. The [Paragraph] is cached and can be retrieved with the
+  /// same [key] by calling [getLayoutFromCache].
+  Paragraph performAndCacheLayout(
+    String text,
+    TextStyle style,
+    double textScaleFactor,
+    int key,
+  ) {
     final builder = ParagraphBuilder(style.getParagraphStyle());
-    builder.pushStyle(style.getTextStyle());
+    builder.pushStyle(style.getTextStyle(textScaleFactor: textScaleFactor));
     builder.addText(text);
 
     final paragraph = builder.build();
@@ -25,10 +37,14 @@ class ParagraphCache {
     return paragraph;
   }
 
+  /// Clears the cache. This should be called when the same text and style
+  /// pair no longer produces the same layout. For example, when a font is
+  /// loaded.
   void clear() {
     _cache.clear();
   }
 
+  /// Returns the number of [Paragraph]s in the cache.
   int get length {
     return _cache.length;
   }

+ 21 - 5
lib/src/ui/render.dart

@@ -32,6 +32,7 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
     required EdgeInsets padding,
     required bool autoResize,
     required TerminalStyle textStyle,
+    required double textScaleFactor,
     required TerminalTheme theme,
     required FocusNode focusNode,
     required TerminalCursorType cursorType,
@@ -44,6 +45,7 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
         _padding = padding,
         _autoResize = autoResize,
         _textStyle = textStyle,
+        _textScaleFactor = textScaleFactor,
         _theme = theme,
         _focusNode = focusNode,
         _cursorType = cursorType,
@@ -101,6 +103,15 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
     if (value == _textStyle) return;
     _textStyle = value;
     _updateCharSize();
+    _paragraphCache.clear();
+    markNeedsLayout();
+  }
+
+  double _textScaleFactor;
+  set textScaleFactor(double value) {
+    if (value == _textScaleFactor) return;
+    _textScaleFactor = value;
+    _updateCharSize();
     markNeedsLayout();
   }
 
@@ -168,7 +179,7 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
   /// Updates [_charSize] based on the current [_textStyle]. This should be
   /// called whenever the [_textStyle] changes or the system font changes.
   void _updateCharSize() {
-    _charSize = calcCharSize(_textStyle);
+    _charSize = calcCharSize(_textStyle, _textScaleFactor);
   }
 
   var _stickToBottom = true;
@@ -219,6 +230,7 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
   @override
   void systemFontsDidChange() {
     _updateCharSize();
+    _paragraphCache.clear();
     super.systemFontsDidChange();
   }
 
@@ -386,6 +398,9 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
     );
   }
 
+  /// The cached for cells in the terminal. Should be cleared when the same
+  /// cell no longer produces the same visual output. For example, when
+  /// [_textStyle] is changed, or when the system font changes.
   final _paragraphCache = ParagraphCache(10240);
 
   @override
@@ -493,7 +508,7 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
       _charSize.height,
       PlaceholderAlignment.middle,
     );
-    builder.pushStyle(style.getTextStyle());
+    builder.pushStyle(style.getTextStyle(textScaleFactor: _textScaleFactor));
     builder.addText(composingText);
 
     final paragraph = builder.build();
@@ -576,8 +591,8 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
     final charCode = cellData.content & CellContent.codepointMask;
     if (charCode == 0) return;
 
-    final hash = cellData.getHash();
-    var paragraph = _paragraphCache.getLayoutFromCache(hash);
+    final cacheKey = cellData.getHash() ^ _textScaleFactor.hashCode;
+    var paragraph = _paragraphCache.getLayoutFromCache(cacheKey);
 
     if (paragraph == null) {
       final cellFlags = cellData.flags;
@@ -611,7 +626,8 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
       paragraph = _paragraphCache.performAndCacheLayout(
         char,
         style,
-        hash,
+        _textScaleFactor,
+        cacheKey,
       );
     }
 

BIN
test/src/_goldens/text_scale_factor@1x.png


BIN
test/src/_goldens/text_scale_factor@2x.png


+ 76 - 0
test/src/terminal_view_test.dart

@@ -306,4 +306,80 @@ void main() {
       expect(output.join(), 'abc');
     });
   });
+
+  group(
+    'TerminalView.textScaleFactor',
+    () {
+      testWidgets(
+        'works',
+        (WidgetTester tester) async {
+          final terminal = Terminal();
+
+          final textScaleFactor = ValueNotifier(1.0);
+
+          await tester.pumpWidget(
+            MaterialApp(
+              home: Scaffold(
+                body: ValueListenableBuilder<double>(
+                  valueListenable: textScaleFactor,
+                  builder: (context, textScaleFactor, child) {
+                    return TerminalView(
+                      terminal,
+                      textScaleFactor: textScaleFactor,
+                    );
+                  },
+                ),
+              ),
+            ),
+          );
+
+          terminal.write('Hello World');
+          await tester.pump();
+
+          await expectLater(
+            find.byType(TerminalView),
+            matchesGoldenFile('_goldens/text_scale_factor@1x.png'),
+          );
+
+          textScaleFactor.value = 2.0;
+          await tester.pump();
+
+          await expectLater(
+            find.byType(TerminalView),
+            matchesGoldenFile('_goldens/text_scale_factor@2x.png'),
+          );
+        },
+        skip: !Platform.isMacOS,
+      );
+
+      testWidgets(
+        'can obtain textScaleFactor from parent',
+        (WidgetTester tester) async {
+          final terminal = Terminal();
+
+          await tester.pumpWidget(
+            MaterialApp(
+              home: Scaffold(
+                body: MediaQuery(
+                  data: const MediaQueryData(textScaleFactor: 2.0),
+                  child: TerminalView(
+                    terminal,
+                  ),
+                ),
+              ),
+            ),
+          );
+
+          terminal.write('Hello World');
+          await tester.pump();
+
+          await expectLater(
+            find.byType(TerminalView),
+            matchesGoldenFile('_goldens/text_scale_factor@2x.png'),
+          );
+        },
+      );
+    },
+    skip: !Platform.isMacOS,
+  );
 }