diff --git a/CHANGELOG.md b/CHANGELOG.md index e39add8..2414f88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 0.8.2 - Support for Flutter 3.38 - Added `Color` methods, `SafeArea` widget, and `showBottomSheet` function. +- Added `Locale`, `TextSpan`, and `Text.rich()` constructor. - Exported multiple wrappers (thanks @Zverik) - Fix typo in example code (thanks @chan150) diff --git a/lib/flutter_eval.dart b/lib/flutter_eval.dart index d5a8812..51e0041 100644 --- a/lib/flutter_eval.dart +++ b/lib/flutter_eval.dart @@ -55,6 +55,8 @@ import 'package:flutter_eval/src/painting/colors.dart'; import 'package:flutter_eval/src/painting/decoration.dart'; import 'package:flutter_eval/src/painting/edge_insets.dart'; import 'package:flutter_eval/src/painting/image_provider.dart'; +import 'package:flutter_eval/src/painting/inline_span.dart'; +import 'package:flutter_eval/src/painting/text_span.dart'; import 'package:flutter_eval/src/painting/text_style.dart'; import 'package:flutter_eval/src/rendering.dart'; import 'package:flutter_eval/src/rendering/box.dart'; @@ -67,6 +69,7 @@ import 'package:flutter_eval/src/scheduler/ticker.dart'; import 'package:flutter_eval/src/services.dart'; import 'package:flutter_eval/src/sky_engine/ui/geometry.dart'; import 'package:flutter_eval/src/sky_engine/ui/image.dart'; +import 'package:flutter_eval/src/sky_engine/ui/locale.dart'; import 'package:flutter_eval/src/sky_engine/ui/painting.dart'; import 'package:flutter_eval/src/sky_engine/ui/pointer.dart'; import 'package:flutter_eval/src/sky_engine/ui/text.dart'; @@ -250,6 +253,9 @@ class FlutterEvalPlugin implements EvalPlugin { $KeyRepeatEvent.$declaration, $BottomSheet.$declaration, $SafeArea.$declaration, + $Locale.$declaration, + $InlineSpan.$declaration, + $TextSpan.$declaration, ]; for (final cls in classes) { @@ -383,6 +389,8 @@ class FlutterEvalPlugin implements EvalPlugin { $IconAlignment.configureForRuntime(runtime); $BottomSheet.configureForRuntime(runtime); $SafeArea.configureForRuntime(runtime); + $TextSpan.configureForRuntime(runtime); + $Locale.configureForRuntime(runtime); $showModalBottomSheetFn.configureForRuntime(runtime); @@ -546,6 +554,8 @@ class FlutterEvalPlugin implements EvalPlugin { 'package:flutter/src/widgets/spacer.dart', 'Spacer.', $Spacer.$new) ..registerBridgeFunc( 'package:flutter/src/widgets/text.dart', 'Text.', $Text.$new) + ..registerBridgeFunc( + 'package:flutter/src/widgets/text.dart', 'Text.rich', $Text.$rich) ..registerBridgeFunc('package:flutter/src/widgets/container.dart', 'Container.', $Container.$new) ..registerBridgeFunc('package:flutter/src/widgets/editable_text.dart', diff --git a/lib/painting.dart b/lib/painting.dart index aa02b4c..afa6747 100644 --- a/lib/painting.dart +++ b/lib/painting.dart @@ -12,3 +12,5 @@ export 'src/painting/decoration.dart'; export 'src/painting/edge_insets.dart'; export 'src/painting/image_provider.dart'; export 'src/painting/text_style.dart'; +export 'src/painting/inline_span.dart'; +export 'src/painting/text_span.dart'; diff --git a/lib/src/painting.dart b/lib/src/painting.dart index 09b5366..7406576 100644 --- a/lib/src/painting.dart +++ b/lib/src/painting.dart @@ -13,4 +13,6 @@ export 'src/painting/decoration.dart'; export 'src/painting/edge_insets.dart'; export 'src/painting/image_provider.dart'; export 'src/painting/text_style.dart'; +export 'src/painting/inline_span.dart'; +export 'src/painting/text_span.dart'; '''; diff --git a/lib/src/painting/inline_span.dart b/lib/src/painting/inline_span.dart new file mode 100644 index 0000000..7ac35f3 --- /dev/null +++ b/lib/src/painting/inline_span.dart @@ -0,0 +1,68 @@ +import 'package:dart_eval/dart_eval_bridge.dart'; +import 'package:dart_eval/stdlib/core.dart'; +import 'package:flutter/material.dart' show InlineSpan; +import 'package:flutter_eval/painting.dart' show $TextStyle; + +/// dart_eval wrapper binding for [InlineSpan] +class $InlineSpan implements $Instance { + /// Configure this class for use in a [Runtime] + static void configureForRuntime(Runtime runtime) {} + + /// Compile-time type specification of [$InlineSpan] + static const $spec = BridgeTypeSpec( + 'package:flutter/src/painting/inline_span.dart', + 'InlineSpan', + ); + + /// Compile-time type declaration of [$InlineSpan] + static const $type = BridgeTypeRef($spec); + + /// Compile-time class declaration of [$InlineSpan] + static const $declaration = BridgeClassDef( + BridgeClassType($type, isAbstract: true), + constructors: { + '': BridgeConstructorDef( + BridgeFunctionDef( + returns: BridgeTypeAnnotation($type), + namedParams: [ + BridgeParameter( + 'style', + BridgeTypeAnnotation( + $TextStyle.$type, + nullable: true, + ), + true, + ), + ], + params: [], + ), + ), + }, + wrap: true, + bridge: false, + ); + + final $Instance _superclass; + + @override + final InlineSpan $value; + + @override + InlineSpan get $reified => $value; + + /// Wrap a [InlineSpan] in a [$InlineSpan] + $InlineSpan.wrap(this.$value) : _superclass = $Object($value); + + @override + int $getRuntimeType(Runtime runtime) => runtime.lookupType($spec); + + @override + $Value? $getProperty(Runtime runtime, String identifier) { + return _superclass.$getProperty(runtime, identifier); + } + + @override + void $setProperty(Runtime runtime, String identifier, $Value value) { + return _superclass.$setProperty(runtime, identifier, value); + } +} diff --git a/lib/src/painting/text_span.dart b/lib/src/painting/text_span.dart new file mode 100644 index 0000000..6c0bd25 --- /dev/null +++ b/lib/src/painting/text_span.dart @@ -0,0 +1,136 @@ +import 'package:dart_eval/dart_eval_bridge.dart'; +import 'package:flutter/material.dart' show TextSpan; +import 'package:flutter_eval/painting.dart' show $TextStyle; +import 'package:flutter_eval/src/painting/inline_span.dart'; +import 'package:flutter_eval/src/sky_engine/ui/locale.dart'; + +/// dart_eval wrapper binding for [TextSpan] +class $TextSpan implements $Instance { + /// Configure this class for use in a [Runtime] + static void configureForRuntime(Runtime runtime) { + runtime.registerBridgeFunc( + 'package:flutter/src/painting/text_span.dart', + 'TextSpan.', + $TextSpan.$new, + ); + } + + /// Compile-time type specification of [$TextSpan] + static const $spec = BridgeTypeSpec( + 'package:flutter/src/painting/text_span.dart', + 'TextSpan', + ); + + /// Compile-time type declaration of [$TextSpan] + static const $type = BridgeTypeRef($spec); + + /// Compile-time class declaration of [$TextSpan] + static const $declaration = BridgeClassDef( + BridgeClassType($type, $extends: $InlineSpan.$type), + constructors: { + '': BridgeConstructorDef( + BridgeFunctionDef( + returns: BridgeTypeAnnotation($type), + namedParams: [ + BridgeParameter( + 'text', + BridgeTypeAnnotation( + BridgeTypeRef(CoreTypes.string, []), + nullable: true, + ), + true, + ), + BridgeParameter( + 'children', + BridgeTypeAnnotation( + BridgeTypeRef( + CoreTypes.list, [BridgeTypeAnnotation($InlineSpan.$type)]), + nullable: true, + ), + true, + ), + BridgeParameter( + 'style', + BridgeTypeAnnotation( + $TextStyle.$type, + nullable: true, + ), + true, + ), + BridgeParameter( + 'semanticsLabel', + BridgeTypeAnnotation( + BridgeTypeRef(CoreTypes.string, []), + nullable: true, + ), + true, + ), + BridgeParameter( + 'semanticsIdentifier', + BridgeTypeAnnotation( + BridgeTypeRef(CoreTypes.string, []), + nullable: true, + ), + true, + ), + BridgeParameter( + 'locale', + BridgeTypeAnnotation($Locale.$type, nullable: true), + true, + ), + BridgeParameter( + 'spellOut', + BridgeTypeAnnotation( + BridgeTypeRef(CoreTypes.bool, []), + nullable: true + ), + true, + ), + ], + params: [], + ), + ), + }, + wrap: true, + bridge: false, + ); + + /// Wrapper for the [TextSpan.new] constructor + static $Value? $new(Runtime runtime, $Value? thisValue, List<$Value?> args) { + return $TextSpan.wrap( + TextSpan( + text: args[0]?.$value, + children: (args[1]?.$reified as List?)?.cast(), + style: args[2]?.$value, + semanticsLabel: args[3]?.$value, + semanticsIdentifier: args[4]?.$value, + locale: args[5]?.$value, + spellOut: args[6]?.$value, + ), + ); + } + + final $Instance _superclass; + + @override + final TextSpan $value; + + @override + TextSpan get $reified => $value; + + /// Wrap a [TextSpan] in a [$TextSpan] + $TextSpan.wrap(this.$value) : _superclass = $InlineSpan.wrap($value); + + @override + int $getRuntimeType(Runtime runtime) => runtime.lookupType($spec); + + @override + $Value? $getProperty(Runtime runtime, String identifier) { + return _superclass.$getProperty(runtime, identifier); + } + + @override + void $setProperty(Runtime runtime, String identifier, $Value value) { + return _superclass.$setProperty(runtime, identifier, value); + } +} diff --git a/lib/src/sky_engine/ui/locale.dart b/lib/src/sky_engine/ui/locale.dart new file mode 100644 index 0000000..870e4d2 --- /dev/null +++ b/lib/src/sky_engine/ui/locale.dart @@ -0,0 +1,185 @@ +import 'dart:ui' show Locale; + +import 'package:dart_eval/dart_eval_bridge.dart'; +import 'package:dart_eval/stdlib/core.dart'; + +/// dart_eval wrapper binding for [Locale] +class $Locale implements $Instance { + /// Configure this class for use in a [Runtime] + static void configureForRuntime(Runtime runtime) { + runtime.registerBridgeFunc('dart:ui', 'Locale.', $Locale.$new); + + runtime.registerBridgeFunc( + 'dart:ui', 'Locale.fromSubtags', $Locale.$fromSubtags); + } + + /// Compile-time type specification of [$Locale] + static const $spec = BridgeTypeSpec('dart:ui', 'Locale'); + + /// Compile-time type declaration of [$Locale] + static const $type = BridgeTypeRef($spec); + + /// Compile-time class declaration of [$Locale] + static const $declaration = BridgeClassDef( + BridgeClassType($type), + constructors: { + '': BridgeConstructorDef( + BridgeFunctionDef( + returns: BridgeTypeAnnotation($type), + namedParams: [], + params: [ + BridgeParameter( + '_languageCode', + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string, [])), + false, + ), + BridgeParameter( + '_countryCode', + BridgeTypeAnnotation( + BridgeTypeRef(CoreTypes.string, []), + nullable: true, + ), + true, + ), + ], + ), + ), + 'fromSubtags': BridgeConstructorDef( + BridgeFunctionDef( + returns: BridgeTypeAnnotation($type), + namedParams: [ + BridgeParameter( + 'languageCode', + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string, [])), + true, + ), + BridgeParameter( + 'scriptCode', + BridgeTypeAnnotation( + BridgeTypeRef(CoreTypes.string, []), + nullable: true, + ), + true, + ), + BridgeParameter( + 'countryCode', + BridgeTypeAnnotation( + BridgeTypeRef(CoreTypes.string, []), + nullable: true, + ), + true, + ), + ], + params: [], + ), + ), + }, + methods: { + 'toLanguageTag': BridgeMethodDef( + BridgeFunctionDef( + returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string, [])), + namedParams: [], + params: [], + ), + ), + }, + getters: { + 'languageCode': BridgeMethodDef( + BridgeFunctionDef( + returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string, [])), + namedParams: [], + params: [], + ), + ), + 'countryCode': BridgeMethodDef( + BridgeFunctionDef( + returns: BridgeTypeAnnotation( + BridgeTypeRef(CoreTypes.string, []), + nullable: true, + ), + namedParams: [], + params: [], + ), + ), + }, + setters: {}, + fields: { + 'scriptCode': BridgeFieldDef( + BridgeTypeAnnotation( + BridgeTypeRef(CoreTypes.string, []), + nullable: true, + ), + ), + }, + wrap: true, + bridge: false, + ); + + /// Wrapper for the [Locale.new] constructor + static $Value? $new(Runtime runtime, $Value? thisValue, List<$Value?> args) { + return $Locale.wrap(Locale(args[0]!.$value, args[1]?.$value)); + } + + /// Wrapper for the [Locale.fromSubtags] constructor + static $Value? $fromSubtags( + Runtime runtime, + $Value? thisValue, + List<$Value?> args, + ) { + return $Locale.wrap( + Locale.fromSubtags( + languageCode: args[0]?.$value ?? 'und', + scriptCode: args[1]?.$value, + countryCode: args[2]?.$value, + ), + ); + } + + @override + final Locale $value; + + @override + Locale get $reified => $value; + + /// Wrap a [Locale] in a [$Locale] + $Locale.wrap(this.$value); + + @override + int $getRuntimeType(Runtime runtime) => runtime.lookupType($spec); + + @override + $Value? $getProperty(Runtime runtime, String identifier) { + switch (identifier) { + case 'scriptCode': + final scriptCode = $value.scriptCode; + return scriptCode == null ? const $null() : $String(scriptCode); + + case 'languageCode': + return $String($value.languageCode); + + case 'countryCode': + final countryCode = $value.countryCode; + return countryCode == null ? const $null() : $String(countryCode); + + case 'toLanguageTag': + return __toLanguageTag; + } + throw UnimplementedError(); + } + + static const $Function __toLanguageTag = $Function(_toLanguageTag); + static $Value? _toLanguageTag( + Runtime runtime, + $Value? target, + List<$Value?> args, + ) { + final self = target! as $Locale; + final result = self.$value.toLanguageTag(); + return $String(result); + } + + @override + void $setProperty(Runtime runtime, String identifier, $Value value) { + throw UnimplementedError(); + } +} diff --git a/lib/src/widgets/text.dart b/lib/src/widgets/text.dart index 486e6cf..14ca179 100644 --- a/lib/src/widgets/text.dart +++ b/lib/src/widgets/text.dart @@ -2,6 +2,7 @@ import 'package:dart_eval/dart_eval_bridge.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_eval/src/foundation/key.dart'; +import 'package:flutter_eval/src/painting/inline_span.dart'; import 'package:flutter_eval/src/painting/text_style.dart'; import 'package:flutter_eval/src/widgets/framework.dart'; @@ -24,7 +25,17 @@ class $Text implements Text, $Instance { 'key', BridgeTypeAnnotation($Key.$type, nullable: true), true), BridgeParameter('style', BridgeTypeAnnotation($TextStyle.$type, nullable: true), true), - ])) + ])), + 'rich': BridgeConstructorDef( + BridgeFunctionDef(returns: BridgeTypeAnnotation($type), params: [ + BridgeParameter( + 'textSpan', BridgeTypeAnnotation($InlineSpan.$type), false), + ], namedParams: [ + BridgeParameter( + 'key', BridgeTypeAnnotation($Key.$type, nullable: true), true), + BridgeParameter('style', + BridgeTypeAnnotation($TextStyle.$type, nullable: true), true), + ])), }, wrap: true); @@ -40,6 +51,14 @@ class $Text implements Text, $Instance { )); } + static $Value? $rich(Runtime runtime, $Value? target, List<$Value?> args) { + return $Text.wrap(Text.rich( + args[0]!.$value, + key: args[1]?.$value, + style: args[2]?.$value, + )); + } + @override final Text $value; diff --git a/lib/ui.dart b/lib/ui.dart index 1cf6547..a2dd6ff 100644 --- a/lib/ui.dart +++ b/lib/ui.dart @@ -5,4 +5,5 @@ export 'src/sky_engine/ui/image.dart'; export 'src/sky_engine/ui/key.dart'; export 'src/sky_engine/ui/painting.dart'; export 'src/sky_engine/ui/pointer.dart'; +export 'src/sky_engine/ui/locale.dart'; export 'src/sky_engine/ui/text.dart'; diff --git a/test/flutter_eval_test.dart b/test/flutter_eval_test.dart index 58e0a57..517a078 100644 --- a/test/flutter_eval_test.dart +++ b/test/flutter_eval_test.dart @@ -259,6 +259,38 @@ void main() { expect((result.$value as Stack).children[1], isA()); }); + test('Rich text', () { + final program = compiler.compile({ + 'example': { + 'main.dart': ''' + import 'package:flutter/material.dart'; + + Widget main() { + return Text.rich( + TextSpan(children: [ + TextSpan(text: 'first'), + TextSpan(text: 'second', style: TextStyle(color: Colors.red)), + ]), + ); + } + ''' + } + }); + final runtime = Runtime(program.write().buffer.asByteData()); + runtime.addPlugin(flutterEvalPlugin); + final result = runtime.executeLib('package:example/main.dart', 'main'); + expect(result, isNotNull); + expect(result.$value, isA()); + final text = result.$value as Text; + expect(text.textSpan, isNotNull); + expect(text.textSpan, isA()); + expect(text.textSpan!.toPlainText(), equals('firstsecond')); + final span = text.textSpan as TextSpan; + expect(span.children, isNotNull); + expect(span.children!.length, equals(2)); + expect(span.children![1].style, isNotNull); + }); + test('ClipRRect', () { final program = compiler.compile({ 'example': {