/// Visual browser for decoded VGA pictures and UI art. library; import 'dart:async'; import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:super_clipboard/super_clipboard.dart'; import 'package:wolf_3d_dart/wolf_3d_data_types.dart'; import 'package:wolf_3d_renderer/wolf_3d_asset_painter.dart'; /// Shows each VGA image extracted from the currently selected game data set. class VgaGallery extends StatelessWidget { /// Raw VGA images decoded from the active asset pack. final List images; /// Creates the gallery for [images]. const VgaGallery({super.key, required this.images}); Future _buildVgaUiImage(VgaImage image) { final completer = Completer(); final Uint8List pixels = Uint8List(image.width * image.height * 4); for (int y = 0; y < image.height; y++) { for (int x = 0; x < image.width; x++) { final int colorByte = image.decodePixel(x, y); final int abgr = ColorPalette.vga32Bit[colorByte]; final int offset = (y * image.width + x) * 4; pixels[offset] = abgr & 0xFF; // R pixels[offset + 1] = (abgr >> 8) & 0xFF; // G pixels[offset + 2] = (abgr >> 16) & 0xFF; // B pixels[offset + 3] = (abgr >> 24) & 0xFF; // A } } ui.decodeImageFromPixels( pixels, image.width, image.height, ui.PixelFormat.rgba8888, (ui.Image decoded) => completer.complete(decoded), ); return completer.future; } Future _copyImageToClipboard(BuildContext context, int index) async { final clipboard = SystemClipboard.instance; if (clipboard == null) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Clipboard API is unavailable here.')), ); } return; } final VgaImage image = images[index]; ui.Image? uiImage; try { uiImage = await _buildVgaUiImage(image); final ByteData? pngData = await uiImage.toByteData( format: ui.ImageByteFormat.png, ); if (pngData == null) { throw StateError('Failed to encode image as PNG.'); } final item = DataWriterItem(); item.add(Formats.png(pngData.buffer.asUint8List())); await clipboard.write([item]); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Copied VGA image #$index to clipboard.')), ); } } catch (error) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Copy failed: $error')), ); } } finally { uiImage?.dispose(); } } void _showImagePreviewDialog(BuildContext context, int index) { final VgaImage image = images[index]; showDialog( context: context, builder: (dialogContext) { return AlertDialog( title: Text( 'Index: $index ${image.width} x ${image.height}', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, ), ), actions: [ TextButton( onPressed: () => Navigator.of(dialogContext).pop(), child: const Text('Close'), ), ElevatedButton.icon( onPressed: () => _copyImageToClipboard(dialogContext, index), icon: const Icon(Icons.copy), label: const Text('Copy'), ), ], content: Center( child: AspectRatio( aspectRatio: image.width / image.height, child: WolfAssetPainter.vga(image), ), ), ); }, ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("VGA Image Gallery")), backgroundColor: Colors.black, body: GridView.builder( gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 150, crossAxisSpacing: 8, mainAxisSpacing: 8, ), itemCount: images.length, itemBuilder: (context, index) { return Card( color: Colors.blueGrey, child: InkWell( onTap: () => _showImagePreviewDialog(context, index), child: Column( mainAxisAlignment: MainAxisAlignment.center, spacing: 8, children: [ Text( "Index: $index\n${images[index].width} x ${images[index].height}", style: const TextStyle(color: Colors.white, fontSize: 12), textAlign: TextAlign.center, ), Expanded( child: Center( child: AspectRatio( aspectRatio: images[index].width / images[index].height, child: WolfAssetPainter.vga(images[index]), ), ), ), ], ), ), ); }, ), ); } }