- Added support for new plugins: IrondashEngineContext and SuperNativeExtensions in the Flutter plugin registrant. - Updated CMake configuration to include new plugins. - Introduced a new dependency, super_clipboard, in pubspec.yaml. - Enhanced the WolfEngine to set the menu background color. - Implemented keyboard shortcuts for renderer mode toggling and ASCII theme cycling in CLI input handling. - Updated menu manager to include a universal menu background color. - Refactored ASCII and Sixel renderers to utilize the new menu background color and improved header drawing logic. - Simplified the drawing of menu options sidebars and header bars across different renderers. - Improved the layout and centering of menu titles in the header bar. Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
168 lines
5.1 KiB
Dart
168 lines
5.1 KiB
Dart
/// 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<VgaImage> images;
|
|
|
|
/// Creates the gallery for [images].
|
|
const VgaGallery({super.key, required this.images});
|
|
|
|
Future<ui.Image> _buildVgaUiImage(VgaImage image) {
|
|
final completer = Completer<ui.Image>();
|
|
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<void> _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<void>(
|
|
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]),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|