pixelflux_codecerebrum 0.0.1
pixelflux_codecerebrum: ^0.0.1 copied to clipboard
Official Flutter package of PixelFlux app. Render your PixelFlux (.pxlflux) files in Flutter with full animation control.
example/main.dart
import 'package:flutter/material.dart';
import 'package:pixelflux_codecerebrum/pixelflux_codecerebrum.dart';
void main() {
runApp(const PixelFluxDemoApp());
}
class PixelFluxDemoApp extends StatelessWidget {
const PixelFluxDemoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'PixelFlux Demo',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF6750A4),
brightness: Brightness.dark,
),
cardTheme: CardThemeData(
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
),
home: const PixelFluxHomePage(),
);
}
}
class PixelFluxHomePage extends StatefulWidget {
const PixelFluxHomePage({super.key});
@override
State<PixelFluxHomePage> createState() => _PixelFluxHomePageState();
}
class _PixelFluxHomePageState extends State<PixelFluxHomePage> {
final controller = PixelFluxController();
PixelFluxProject? project;
bool isLoading = true;
@override
void initState() {
super.initState();
_load();
// Listen to controller status changes to update UI
controller.status.addListener(_onStatusChanged);
}
void _onStatusChanged() {
if (mounted) {
setState(() {});
}
}
Future<void> _load() async {
try {
// Swap between image / video for testing
// project = await PixelFluxLoader.loadFromAsset("assets/sample_image.pxlflux");
project = await PixelFluxLoader.loadFromAsset("assets/sample_video.pxlflux");
} catch (e) {
debugPrint('Error loading project: $e');
} finally {
if (mounted) {
setState(() {
isLoading = false;
});
}
}
}
@override
void dispose() {
controller.status.removeListener(_onStatusChanged);
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
appBar: AppBar(
title: const Text(
"PixelFlux Studio",
style: TextStyle(fontWeight: FontWeight.bold),
),
centerTitle: true,
backgroundColor: Colors.transparent,
elevation: 0,
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Theme.of(context).colorScheme.primaryContainer,
Theme.of(context).colorScheme.secondaryContainer,
],
),
),
),
),
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Theme.of(context).colorScheme.surface,
Theme.of(context).colorScheme.surface.withOpacity(0.8),
],
),
),
child: Center(
child: isLoading
? _buildLoadingWidget()
: project == null
? _buildErrorWidget()
: _buildContent(),
),
),
);
}
Widget _buildLoadingWidget() {
return Card(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const CircularProgressIndicator(),
const SizedBox(height: 16),
Text(
'Loading PixelFlux Project...',
style: Theme.of(context).textTheme.titleMedium,
),
],
),
),
);
}
Widget _buildErrorWidget() {
return Card(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.error_outline,
size: 48,
color: Theme.of(context).colorScheme.error,
),
const SizedBox(height: 16),
Text(
'Failed to load project',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
FilledButton.tonal(
onPressed: () {
setState(() {
isLoading = true;
});
_load();
},
child: const Text('Retry'),
),
],
),
),
);
}
Widget _buildContent() {
final p = project!;
return SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Canvas Card
Card(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: PixelFluxCanvas(
project: p,
controller: controller,
canvasWidth: 320,
canvasHeight: 240,
pixelSize: 12.0,
showPixelBorders: true,
scale: 1.5,
rotation: 0.3, // ~17 degrees
flipHorizontal: false,
flipVertical: true,
translateX: 20,
translateY: -10,
),
),
),
const SizedBox(height: 16),
Text(
p.isVideo ? 'Video Project' : 'Image Project',
style: Theme.of(context).textTheme.labelLarge?.copyWith(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
const SizedBox(height: 16),
// Controls Card (only for video)
if (p.isVideo) _buildControlsCard(),
],
),
);
}
Widget _buildControlsCard() {
return Card(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Playback Controls',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
// Playback buttons
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildControlButton(
icon: Icons.play_arrow_rounded,
onPressed: controller.play,
tooltip: 'Play',
),
_buildControlButton(
icon: Icons.pause_rounded,
onPressed: controller.pause,
tooltip: 'Pause',
),
_buildControlButton(
icon: Icons.stop_rounded,
onPressed: controller.stop,
tooltip: 'Stop',
),
_buildControlButton(
icon: Icons.swap_horiz_rounded,
onPressed: () => controller.reverse(),
tooltip: 'Reverse',
),
],
),
const SizedBox(height: 24),
// Loop mode selector
ValueListenableBuilder(
valueListenable: controller.status,
builder: (context, status, _) {
return Row(
children: [
Icon(
Icons.loop_rounded,
size: 20,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 8),
Text(
'Loop Mode:',
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(width: 12),
Expanded(
child: DropdownButtonFormField<LoopMode>(
value: status.loopMode,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
filled: true,
fillColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.3),
),
onChanged: (m) => controller.setLoopMode(m ?? LoopMode.loop),
items: const [
DropdownMenuItem(
value: LoopMode.once,
child: Text("Once"),
),
DropdownMenuItem(
value: LoopMode.loop,
child: Text("Loop"),
),
DropdownMenuItem(
value: LoopMode.pingPong,
child: Text("Ping-Pong"),
),
],
),
),
],
);
},
),
const SizedBox(height: 20),
// Speed slider
Row(
children: [
Icon(
Icons.speed_rounded,
size: 20,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 8),
Text(
'Speed:',
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ValueListenableBuilder(
valueListenable: controller.status,
builder: (context, status, _) {
return Slider(
value: status.speed,
onChanged: (v) => controller.setSpeed(v),
min: 0.1,
max: 4.0,
divisions: 39,
label: "${status.speed.toStringAsFixed(1)}x",
);
},
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'0.1x',
style: Theme.of(context).textTheme.bodySmall,
),
ValueListenableBuilder(
valueListenable: controller.status,
builder: (context, status, _) {
return Text(
'${status.speed.toStringAsFixed(1)}x',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
);
},
),
Text(
'4.0x',
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
],
),
),
],
),
],
),
),
);
}
Widget _buildControlButton({
required IconData icon,
required VoidCallback onPressed,
required String tooltip,
}) {
return Tooltip(
message: tooltip,
child: FilledButton.tonal(
onPressed: onPressed,
style: FilledButton.styleFrom(
shape: const CircleBorder(),
padding: const EdgeInsets.all(16),
),
child: Icon(icon, size: 24),
),
);
}
}