mirror of
https://github.com/rive-app/rive-flutter.git
synced 2025-11-01 20:19:39 +08:00
* refactor!(flutter): scaling with dpr on Rive Renderer * refactor(flutter)!: render object texture creation and paint * chore(editor): fix dragon login mouse follow * docs(flutter): update CHANGELOG * feat(flutter): apply transform scaling on RivePanel * chore(flutter): remove repaint boundary to fix goldens data bind artboards rcp file (#10214) c542b9b7ac * make file ref counted * migrate File to refcnt * add bindable artboard class to keep file reference * feat(unity): support rcp file and BindableArtboard class (#10228) * refactor(apple): use updated file bindable artboard apis (#10229) * update command queue to support bindable artboards * use bindable artboards on command queue * self manage view model instances in js runtime * change remaining viewModelInstanceRuntimes to rcp * refactor(apple): update view model instances to use rcp (#10298) * refactor(unity): support rcp ViewModelInstanceRuntime (#10309) * rebase fix * deprecate getArtboard in favor of getBindableArtboard * fix merge * remove unused lambda capture * fix rive binding * feat(Android): RCP File, VMI, and add bindable artboards (#10456) * refactor: C++ refactors - Import from long (incorrect) -> jlong - Header clang-tidy fix - Use reinterpret_cast instead of C-style cast - Break out some variables instead of long one liners - Use auto - Remove unused env and thisObj # Conflicts: # packages/runtime_android/kotlin/src/main/cpp/src/helpers/general.cpp * docs: Improve documentation on the File class * feat: Support bindable artboard type and RCP VMIs # Conflicts: # packages/runtime_android/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveDataBindingTest.kt * feat: Support RCP files * refactor: Change from +1/-1 ref to just release * fix: Moved to the more appropriate null pointer for GetStringUTFChars * replace unref with release test: Add an android_gms_vulkan run to browserstack (#10669) f7613dbf35 Vulkan is ready to stabilize. Let's get it on our nightly runs. Also disable "atomic" mode on Android unless requested explicitly. Barriers tend to be expensive on Android and MSAA is usually cheap; let's stabilize MSAA first. fix(vk): Properly preserve render targets when using MSAA (#10630) 5df1a42463 * fix(vk): Properly preserve render targets when using MSAA Unfortunately, Vulkan does not provide a mechanism to initialize our (transient) MSAA texture with the contents of the (non-MSAA) renderTarget texture at the beginning of a render pass. To work around this limitation while supporting LoadAction::preserveRenderTarget, we literally draw the previous render target contents into the framebuffer at the beginning of the pass. * Fix one more barrier fix(vk, d3d12): Properly apply draw batch barriers on pipeline failures (#10667) 5efb71a88a If a pipeline failed to acquire for a draw batch that required barriers of any kind, we were skipping the barriers as well as attempting to draw. Now the code will apply the barriers and only skip drawing. chore: add pointer exit support (#10595) d999d6a22c * add pointer exit support scripting: color and string view model property (#10663) 3acbdfcba7 Description Adds support for passing a string and color vm property to the scripting engine. Adds support for defining color and string as inputs. feat: Integrate glfw into the premake build (#10656) 653c8c6040 Build GLFW with premake instead of requiring the user to call out into a custom script thatbuses their cmake. feat(scripting): split code panels (#10655) 9d8b49152e * feature(scripting): command palette * feature: adding split pane saving * chore: update to latest luau * fix: pixel correct scrollbars * feature: reload scroll position of script pane * feature: module titles on panes * chore: merging command palettes * fix: cleanup * chore: refactor searchbar tests * fix: failing test * chore: cleanup * chore: cleanup * chore: cleanup unused commented code * feature: hover for command palette items * chore: cleanup mocks Fix MSVC toolchain path_fiddle builds (#10661) 0f0d7c5f81 Our MSVC builds are building with C++latest (instead of C++17), and what that means is that filesystem::path::u8string returns a std::u8string instead of std::string (see https://en.cppreference.com/w/cpp/filesystem/path/string.html), causing a compilation failure for the shader hotload rebuild command. This change adds back in the reinterpret_cast to a char pointer (but still stores the temp string correctly). Add loop option to GM (#10649) 0121fd0174 * add loopCount * add static * Update gmmain.cpp * Change while to for Co-authored-by: Gordon <pggordonhayes@gmail.com>
449 lines
14 KiB
Dart
449 lines
14 KiB
Dart
// ignore_for_file: deprecated_member_use
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:rive_example/advanced/advanced.dart';
|
|
import 'package:rive_example/colors.dart';
|
|
import 'package:rive_example/examples/examples.dart';
|
|
import 'package:rive/rive.dart' as rive;
|
|
|
|
void main() async {
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|
await rive.RiveNative.init();
|
|
|
|
runApp(
|
|
MaterialApp(
|
|
title: 'Rive Example',
|
|
home: const RiveExampleApp(),
|
|
darkTheme: ThemeData(
|
|
fontFamily: 'JetBrainsMono',
|
|
brightness: Brightness.dark,
|
|
scaffoldBackgroundColor: backgroundColor,
|
|
appBarTheme: const AppBarTheme(backgroundColor: appBarColor),
|
|
colorScheme: ColorScheme.fromSwatch(brightness: Brightness.dark)
|
|
.copyWith(primary: primaryColor),
|
|
),
|
|
themeMode: ThemeMode.dark,
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Determines which factory/renderer to use for the Rive examples.
|
|
///
|
|
/// In your app you can combine the usage of the Rive Renderer and the Flutter
|
|
/// Renderer. For this example app we have a static variable to determine which
|
|
/// factory to use app wide.
|
|
///
|
|
/// - `rive` uses the Rive Renderer
|
|
/// - `flutter` uses the Flutter Renderer (Skia / Impeller)
|
|
enum RiveFactoryToUse {
|
|
rive,
|
|
flutter,
|
|
}
|
|
|
|
/// An example application demoing Rive.
|
|
class RiveExampleApp extends StatefulWidget {
|
|
const RiveExampleApp({Key? key}) : super(key: key);
|
|
|
|
static RiveFactoryToUse factoryToUse = RiveFactoryToUse.rive;
|
|
|
|
static rive.Factory get getCurrentFactory => switch (factoryToUse) {
|
|
RiveFactoryToUse.rive => rive.Factory.rive,
|
|
RiveFactoryToUse.flutter => rive.Factory.flutter,
|
|
};
|
|
|
|
@override
|
|
State<RiveExampleApp> createState() => _RiveExampleAppState();
|
|
}
|
|
|
|
class _RiveExampleAppState extends State<RiveExampleApp> {
|
|
// ScrollController for the CustomScrollView
|
|
final ScrollController _scrollController = ScrollController();
|
|
|
|
// Examples organized into sections
|
|
final _sections = [
|
|
const _Section(
|
|
'Getting Started',
|
|
[
|
|
_Page('Rive Widget', ExampleRiveWidget(),
|
|
'Simple example usage of the Rive widget with common parameters.'),
|
|
_Page('Rive Widget Builder', ExampleRiveWidgetBuilder(),
|
|
'Example usage of the Rive builder widget with common parameters.'),
|
|
_Page('Rive Panel [Shared Texture]', ExampleRivePanel(),
|
|
'Example usage of the Shared Texture View widget.'),
|
|
],
|
|
),
|
|
const _Section(
|
|
'Rive Features',
|
|
[
|
|
_Page('Data Binding', ExampleDataBinding(),
|
|
'Example using Rive data binding at runtime.'),
|
|
_Page('Data Binding - Images', ExampleDataBindingImages(),
|
|
'Example using Rive data binding images at runtime.'),
|
|
_Page('Data Binding - Artboards', ExampleDataBindingArtboards(),
|
|
'Example using Rive data binding artboards at runtime.'),
|
|
_Page('Data Binding - Lists', ExampleDataBindingLists(),
|
|
'Example using Rive data binding lists at runtime.'),
|
|
_Page('Responsive Layouts', ExampleResponsiveLayouts(),
|
|
'Create responsive Rive graphics that adapt to screen size.'),
|
|
_Page('Events', ExampleEvents(), 'Handle Rive events.'),
|
|
_Page('Audio', ExampleRiveAudio(), 'Example Rive file with audio.'),
|
|
],
|
|
),
|
|
const _Section(
|
|
'Asset Loading',
|
|
[
|
|
_Page('Network .riv Asset', ExampleNetworkAsset(),
|
|
'Load and display Rive graphics from network URLs.'),
|
|
_Page('Out-of-band Assets', ExampleOutOfBandAssetLoading(),
|
|
'Load Rive files with external assets (images, audio) separately.'),
|
|
_Page(
|
|
'Out-of-band Assets - Cached',
|
|
ExampleOutOfBandCachedAssetLoading(),
|
|
'Load Rive files with cached external assets for better immediate availability.',
|
|
),
|
|
],
|
|
),
|
|
const _Section(
|
|
'Painters [Advanced]',
|
|
[
|
|
_Page('State Machine Painter', ExampleStateMachinePainter(),
|
|
'Advanced: Custom painter for state machines.'),
|
|
_Page('Single Animation Painter', ExampleSingleAnimationPainter(),
|
|
'Advanced: Custom painter for single animation playback.'),
|
|
_Page('Centaur Game', CentaurGameWidget(), 'Advanced: Centaur Game.'),
|
|
],
|
|
),
|
|
const _Section(
|
|
'Flutter Concepts/Integration',
|
|
[
|
|
// _Page('Flutter Lists', Todo(),
|
|
// 'Integrate Rive graphics with Flutter list widgets.'),
|
|
_Page(
|
|
'Pause/Play', ExamplePausePlay(), 'Pause and play Rive graphics.'),
|
|
_Page('Flutter Hit Test + Cursor Behaviour', ExampleHitTestBehaviour(),
|
|
'Specifying hit test and cursor behaviour.'),
|
|
_Page('Flutter Ticker Mode', ExampleTickerMode(),
|
|
'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(),
|
|
// 'Manage Rive state with Flutter state management.'),
|
|
// _Page('Flutter Localization', Todo(),
|
|
// 'Localize Rive graphics for different languages.'),
|
|
// _Page('Flutter Internationalization', Todo(),
|
|
// 'Internationalize Rive graphics with Flutter i18n.'),
|
|
],
|
|
),
|
|
const _Section(
|
|
'Legacy Features [Use data binding instead]',
|
|
[
|
|
_Page('Inputs [Nested]', ExampleInputs(),
|
|
'Legacy: Handle input [nested] controls in Rive graphics.'),
|
|
_Page('Text Runs [Nested]', ExampleTextRuns(),
|
|
'Legacy: Handle text runs [nested] components in Rive graphics.'),
|
|
],
|
|
),
|
|
];
|
|
|
|
@override
|
|
void dispose() {
|
|
_scrollController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(title: const Text('Rive Examples')),
|
|
body: Column(children: [
|
|
Expanded(
|
|
child: Scrollbar(
|
|
controller: _scrollController,
|
|
child: CustomScrollView(
|
|
controller: _scrollController,
|
|
slivers: [
|
|
SliverPadding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
sliver: SliverList(
|
|
delegate: SliverChildBuilderDelegate(
|
|
(context, index) {
|
|
// Calculate which section and item we're at
|
|
int itemIndex = index;
|
|
|
|
for (int i = 0; i < _sections.length; i++) {
|
|
if (itemIndex == 0) {
|
|
// This is a section header
|
|
return _SectionHeader(_sections[i].title);
|
|
}
|
|
itemIndex--;
|
|
|
|
if (itemIndex < _sections[i].pages.length) {
|
|
// This is a page within the current section
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 16.0),
|
|
child: _NavButton(
|
|
page: _sections[i].pages[itemIndex]),
|
|
);
|
|
}
|
|
itemIndex -= _sections[i].pages.length;
|
|
}
|
|
|
|
return null;
|
|
},
|
|
childCount: _getTotalItemCount(),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
ColoredBox(
|
|
color: Colors.black,
|
|
child: Column(
|
|
children: [
|
|
const SizedBox(height: 16),
|
|
const Text('Factory to use:',
|
|
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
|
const SizedBox(height: 16),
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Wrap(
|
|
alignment: WrapAlignment.center,
|
|
spacing: 16,
|
|
runSpacing: 8,
|
|
children: [
|
|
Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Radio<RiveFactoryToUse>(
|
|
value: RiveFactoryToUse.rive,
|
|
groupValue: RiveExampleApp.factoryToUse,
|
|
onChanged: (value) {
|
|
setState(() {
|
|
RiveExampleApp.factoryToUse =
|
|
value as RiveFactoryToUse;
|
|
});
|
|
},
|
|
),
|
|
const Text('Rive Renderer',
|
|
style: TextStyle(fontSize: 14)),
|
|
],
|
|
),
|
|
Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Radio<RiveFactoryToUse>(
|
|
value: RiveFactoryToUse.flutter,
|
|
groupValue: RiveExampleApp.factoryToUse,
|
|
onChanged: (value) {
|
|
setState(() {
|
|
RiveExampleApp.factoryToUse =
|
|
value as RiveFactoryToUse;
|
|
});
|
|
},
|
|
),
|
|
const Text('Flutter Renderer',
|
|
style: TextStyle(fontSize: 14)),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
]),
|
|
);
|
|
}
|
|
|
|
int _getTotalItemCount() {
|
|
int count = 0;
|
|
for (final section in _sections) {
|
|
count += 1; // Section header
|
|
count += section.pages.length; // Pages in section
|
|
}
|
|
return count;
|
|
}
|
|
}
|
|
|
|
/// Class used to organize demo sections.
|
|
class _Section {
|
|
final String title;
|
|
final List<_Page> pages;
|
|
|
|
const _Section(this.title, this.pages);
|
|
}
|
|
|
|
/// Class used to organize demo pages.
|
|
class _Page {
|
|
final String name;
|
|
final Widget page;
|
|
final String description;
|
|
|
|
const _Page(this.name, this.page, this.description);
|
|
}
|
|
|
|
/// Section header widget with divider.
|
|
class _SectionHeader extends StatelessWidget {
|
|
const _SectionHeader(this.title);
|
|
|
|
final String title;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const SizedBox(height: 16),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
|
child: Text(
|
|
title,
|
|
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
|
color: primaryColor,
|
|
),
|
|
),
|
|
),
|
|
const Divider(
|
|
color: primaryColor,
|
|
thickness: 0.5,
|
|
height: 1,
|
|
),
|
|
const SizedBox(height: 16),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Button to navigate to demo pages with hover overlay.
|
|
class _NavButton extends StatefulWidget {
|
|
const _NavButton({required this.page});
|
|
|
|
final _Page page;
|
|
|
|
@override
|
|
State<_NavButton> createState() => _NavButtonState();
|
|
}
|
|
|
|
class _NavButtonState extends State<_NavButton> {
|
|
bool _isHovered = false;
|
|
OverlayEntry? _overlayEntry;
|
|
|
|
@override
|
|
void dispose() {
|
|
_removeOverlay();
|
|
super.dispose();
|
|
}
|
|
|
|
void _removeOverlay() {
|
|
_overlayEntry?.remove();
|
|
_overlayEntry = null;
|
|
}
|
|
|
|
void _showOverlay() {
|
|
_removeOverlay();
|
|
|
|
final RenderBox renderBox = context.findRenderObject() as RenderBox;
|
|
final position = renderBox.localToGlobal(Offset.zero);
|
|
final size = renderBox.size;
|
|
|
|
_overlayEntry = OverlayEntry(
|
|
builder: (context) => Positioned(
|
|
top: position.dy - 80, // Position above the button
|
|
left: position.dx + (size.width / 2) - 150, // Center horizontally
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
child: Container(
|
|
width: 300,
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.black.withOpacity(0.9),
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(color: primaryColor, width: 1),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.3),
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Text(
|
|
widget.page.description,
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 12,
|
|
height: 1.4,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
Overlay.of(context).insert(_overlayEntry!);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MouseRegion(
|
|
onEnter: (_) {
|
|
setState(() => _isHovered = true);
|
|
_showOverlay();
|
|
},
|
|
onExit: (_) {
|
|
setState(() => _isHovered = false);
|
|
_removeOverlay();
|
|
},
|
|
child: Center(
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: _isHovered ? primaryColor.withOpacity(0.1) : null,
|
|
elevation: _isHovered ? 8 : 2,
|
|
),
|
|
child: SizedBox(
|
|
width: 300,
|
|
child: Center(
|
|
child: Text(
|
|
widget.page.name,
|
|
style: Theme.of(context).textTheme.labelLarge,
|
|
),
|
|
),
|
|
),
|
|
onPressed: () {
|
|
_removeOverlay();
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute<void>(
|
|
builder: (context) => _WrappedPage(page: widget.page),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Scaffold wrapper for the page.
|
|
class _WrappedPage extends StatelessWidget {
|
|
const _WrappedPage({required this.page});
|
|
|
|
final _Page page;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(title: Text(page.name)),
|
|
body: page.page,
|
|
);
|
|
}
|
|
}
|