diff --git a/.rive_head b/.rive_head index 40943f4..2100cb8 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -5466e81cc872efbf4b4e8e14a7582cb020fb670c +1f5e950a28307a6cb100f80b8c922a05a60a77a5 diff --git a/CHANGELOG.md b/CHANGELOG.md index cd7ac3c..3a88323 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,15 @@ ## Upcoming +- Reduced the number of texture allocations made when resizing widgets using `Factory.rive`, improving memory efficiency and performance. +- Enhanced painting and texture creation behavior when resizing widgets or windows with `Factory.rive`, resulting in smoother widget resizing. +- Better aligned the native and web implementations to ensure consistent behavior across all platforms. + ### Fixes +- Fixed an issue where `RivePanel` would not render anything after resizing if all graphics are settled. +- Resolved [issue #498](https://github.com/rive-app/rive-flutter/issues/498) where `Fit.scaleDown` was not working correctly with `Factory.rive`. +- Resolved [an issue](https://community.rive.app/c/support/rive-native-rendering-visible-aliasing-jagged-edges) where upscaling Rive widget transforms resulted in blurry textures when using `Factory.rive`. +- Fixed layout problems that occurred when certain `Fit` modes were used and the user switched to a different DPI screen. - Fixed [487](https://github.com/rive-app/rive-flutter/issues/487) - automatically advances/paints a graphic when updating any view model property or any state machine input. ## 0.14.0-dev.8 diff --git a/example/assets/rive_rendering_test.riv b/example/assets/rive_rendering_test.riv new file mode 100644 index 0000000..12f6542 Binary files /dev/null and b/example/assets/rive_rendering_test.riv differ diff --git a/example/lib/advanced/centaur_example/game_widget.dart b/example/lib/advanced/centaur_example/game_widget.dart index 47fd2bd..c6961c3 100644 --- a/example/lib/advanced/centaur_example/game_widget.dart +++ b/example/lib/advanced/centaur_example/game_widget.dart @@ -72,16 +72,13 @@ class _CentaurGameWidgetState extends State { }, )..requestFocus(), child: MouseRegion( - onHover: (event) => _centaurPainter!.aimAt( - event.localPosition * View.of(context).devicePixelRatio, - ), + onHover: (event) => + _centaurPainter!.aimAt(event.localPosition), child: Listener( behavior: HitTestBehavior.opaque, onPointerDown: _centaurPainter!.pointerDown, - onPointerMove: (event) => _centaurPainter!.aimAt( - event.localPosition * - View.of(context).devicePixelRatio, - ), + onPointerMove: (event) => + _centaurPainter!.aimAt(event.localPosition), child: _renderTexture.widget( painter: _centaurPainter!, ), diff --git a/example/lib/examples/examples.dart b/example/lib/examples/examples.dart index 8aeb1c0..2a159cd 100644 --- a/example/lib/examples/examples.dart +++ b/example/lib/examples/examples.dart @@ -20,3 +20,4 @@ export 'text_runs.dart'; export 'ticker_mode.dart'; export 'time_dilation.dart'; export 'todo.dart'; +export 'transform.dart'; diff --git a/example/lib/examples/rive_panel.dart b/example/lib/examples/rive_panel.dart index f19797d..0a0d560 100644 --- a/example/lib/examples/rive_panel.dart +++ b/example/lib/examples/rive_panel.dart @@ -7,6 +7,7 @@ class ExampleRivePanel extends StatelessWidget { @override Widget build(BuildContext context) { return const RivePanel( + // child: RowExample(), child: ListViewExample(), ); } diff --git a/example/lib/examples/transform.dart b/example/lib/examples/transform.dart new file mode 100644 index 0000000..7295408 --- /dev/null +++ b/example/lib/examples/transform.dart @@ -0,0 +1,102 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:rive/rive.dart'; + +class ExampleTransform extends StatelessWidget { + const ExampleTransform({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded(child: _MyRiveWidget(riveFactory: Factory.rive)), + Expanded(child: _MyRiveWidget(riveFactory: Factory.flutter)), + ], + ), + ), + ); + } +} + +class _MyRiveWidget extends StatefulWidget { + final Factory riveFactory; + + const _MyRiveWidget({required this.riveFactory}); + + @override + State<_MyRiveWidget> createState() => _MyRiveWidgetState(); +} + +class _MyRiveWidgetState extends State<_MyRiveWidget> + with SingleTickerProviderStateMixin { + bool isInitialized = false; + + late final File file; + late final RiveWidgetController controller; + late final ViewModelInstance vmi; + + double scale = 1.5; + + @override + void initState() { + super.initState(); + initRive(); + } + + void initRive() async { + file = (await File.asset( + 'assets/rive_rendering_test.riv', + riveFactory: widget.riveFactory, + ))!; + controller = RiveWidgetController( + file, + artboardSelector: ArtboardSelector.byName('Rive Rendering'), + ); + vmi = controller.dataBind(DataBind.auto()); + + final renderName = vmi.string('rendererName')!; + renderName.value = + widget.riveFactory == Factory.flutter ? 'Flutter' : 'Rive'; + + setState(() => isInitialized = true); + } + + @override + void dispose() { + controller.dispose(); + file.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return isInitialized + ? getTransformRiveWidget() + : const CircularProgressIndicator(); + } + + Widget getTransformRiveWidget() { + return Listener( + onPointerSignal: (pointerSignal) { + if (pointerSignal is PointerScrollEvent) { + final delta = -pointerSignal.scrollDelta.dy; + setState(() { + scale = (scale + delta * 0.008).clamp(0.1, 5.0); + }); + } + }, + child: Transform( + transform: Matrix4.identity()..scale(scale, scale, scale), + alignment: Alignment.center, + // filterQuality: FilterQuality.high, + child: RiveWidget( + controller: controller, + fit: Fit.contain, + ), + ), + ); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index e4433ac..f230b11 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -126,6 +126,8 @@ class _RiveExampleAppState extends State { 'Rive graphics respect Flutter ticker mode.'), _Page('Flutter Time Dilation', ExampleTimeDilation(), 'Rive graphics respect Flutter time dilation.'), + _Page('Flutter Transform', ExampleTransform(), + 'Rive graphics respect Flutter transform.'), // _Page('Flutter Hero Transitions', Todo(), // 'Create smooth transitions between pages with Rive graphics.'), // _Page('Flutter State Management', Todo(), diff --git a/lib/src/painters/widget_controller.dart b/lib/src/painters/widget_controller.dart index b16e573..0e6cd11 100644 --- a/lib/src/painters/widget_controller.dart +++ b/lib/src/painters/widget_controller.dart @@ -146,7 +146,7 @@ base class RiveWidgetController extends BasicArtboardPainter artboardBounds: artboard.bounds, fit: fit, alignment: alignment, - size: lastSize / lastPaintPixelRatio, + size: lastSize, scaleFactor: layoutScaleFactor, ), ); @@ -165,7 +165,7 @@ base class RiveWidgetController extends BasicArtboardPainter artboardBounds: artboard.bounds, fit: fit, alignment: alignment, - size: lastSize / lastPaintPixelRatio, + size: lastSize, scaleFactor: layoutScaleFactor, ); final HitResult hitResult; diff --git a/lib/src/widgets/rive_panel.dart b/lib/src/widgets/rive_panel.dart index 693405d..267863f 100644 --- a/lib/src/widgets/rive_panel.dart +++ b/lib/src/widgets/rive_panel.dart @@ -136,9 +136,7 @@ class _RivePanelState extends State { Widget build(BuildContext context) { return Stack( children: [ - Positioned.fill( - child: _renderTexture!.widget(key: _panelKey), - ), + Positioned.fill(child: _renderTexture!.widget(key: _panelKey)), RiveSharedTexture( panelKey: _panelKey, backgroundColor: widget.backgroundColor, diff --git a/lib/src/widgets/shared_texture_view.dart b/lib/src/widgets/shared_texture_view.dart index 6d19b33..964e75a 100644 --- a/lib/src/widgets/shared_texture_view.dart +++ b/lib/src/widgets/shared_texture_view.dart @@ -91,16 +91,17 @@ class SharedTextureViewRenderer extends LeafRenderObjectWidget { @override void didUnmountRenderObject( - covariant SharedTextureViewRenderObject renderObject, - ) {} + covariant SharedTextureViewRenderObject renderObject) { + renderObject.painter = null; + } } -class SharedTextureViewRenderObject - extends RiveNativeRenderBox +class SharedTextureViewRenderObject extends RiveNativeRenderBox implements SharedTexturePainter { SharedRenderTexture _shared; - SharedTextureViewRenderObject(this._shared) { + SharedTextureViewRenderObject(this._shared) + : super(UnimplementedRenderTexture()) { _shared.texture.onTextureChanged = _onRiveTextureChanged; } @@ -129,6 +130,11 @@ class SharedTextureViewRenderObject // texture Rive draws to. void _onRiveTextureChanged() => markNeedsLayout(); + @override + void paintTexture(double elapsedSeconds, {bool forceShouldAdvance = false}) { + // do nothing. we draw to the shared texture. + } + @override bool get sizedByParent => true; @@ -136,7 +142,10 @@ class SharedTextureViewRenderObject Size computeDryLayout(BoxConstraints constraints) => constraints.smallest; @override - void paint(PaintingContext context, Offset offset) => _shared.schedulePaint(); + void paint(PaintingContext context, Offset offset) { + super.paint(context, offset); + _shared.schedulePaint(); + } ScrollPosition? _scrollPosition; set scrollPosition(ScrollPosition? v) { @@ -176,16 +185,24 @@ class SharedTextureViewRenderObject final renderer = texture.renderer; renderer.save(); - renderer.translate( - globalPosition.dx * devicePixelRatio, - globalPosition.dy * devicePixelRatio, - ); - final scaledSize = size * devicePixelRatio; - final needsAdvance = rivePainter?.paint( - texture, devicePixelRatio, scaledSize, elapsedSeconds) ?? - false; - _shouldAdvance = elapsedSeconds == 0 ? true : needsAdvance; + // Create a single matrix that does: scale(devicePixelRatio) -> translate(position) -> scale(transform) + final scaledTranslateX = globalPosition.dx * devicePixelRatio; + final scaledTranslateY = globalPosition.dy * devicePixelRatio; + renderer.transform(Mat2D.fromScaleAndTranslation( + scaleWidth, scaleHeight, scaledTranslateX, scaledTranslateY)); + _shouldAdvance = rivePainter?.paint( + texture, + devicePixelRatio, + size, + elapsedSeconds, + ) ?? + false; + if (_shouldAdvance) { + restartTickerIfStopped(); + } else { + stopTicker(); + } renderer.restore(); }