feat: Implement game selection in sprite and VGA galleries with GalleryGameSelector

refactor: Update VgaGallery and SpriteGallery to use selected game data
chore: Remove unused plugins from generated plugin registrant and CMake files
chore: Clean up pubspec.yaml by removing super_clipboard dependency

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-20 15:53:24 +01:00
parent ed1e480555
commit 03dd871a46
7 changed files with 212 additions and 128 deletions

View File

@@ -1,93 +1,49 @@
/// 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_flutter/wolf_3d_flutter.dart';
import 'package:wolf_3d_gui/screens/gallery_game_selector.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;
class VgaGallery extends StatefulWidget {
/// Shared app facade used to access available game data sets.
final Wolf3d wolf3d;
/// Creates the gallery for [images].
const VgaGallery({super.key, required this.images});
/// Creates the gallery for the currently selected or browsed game.
const VgaGallery({super.key, required this.wolf3d});
Future<ui.Image> _buildVgaUiImage(VgaImage image) {
final completer = Completer<ui.Image>();
final Uint8List pixels = Uint8List(image.width * image.height * 4);
@override
State<VgaGallery> createState() => _VgaGalleryState();
}
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
}
}
class _VgaGalleryState extends State<VgaGallery> {
late WolfensteinData _selectedGame;
ui.decodeImageFromPixels(
pixels,
image.width,
image.height,
ui.PixelFormat.rgba8888,
(ui.Image decoded) => completer.complete(decoded),
);
return completer.future;
List<VgaImage> get _images => _selectedGame.vgaImages;
@override
void initState() {
super.initState();
_selectedGame =
widget.wolf3d.maybeActiveGame ?? widget.wolf3d.availableGames.first;
}
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.')),
);
}
void _selectGame(WolfensteinData game) {
if (identical(_selectedGame, game)) {
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();
}
widget.wolf3d.setActiveGame(game);
setState(() {
_selectedGame = game;
});
}
void _showImagePreviewDialog(BuildContext context, int index) {
final VgaImage image = images[index];
final VgaImage image = _images[index];
showDialog<void>(
context: context,
@@ -105,11 +61,6 @@ class VgaGallery extends StatelessWidget {
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(
@@ -125,7 +76,17 @@ class VgaGallery extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("VGA Image Gallery")),
appBar: AppBar(
title: const Text('VGA Image Gallery'),
actions: [
if (widget.wolf3d.availableGames.length > 1)
GalleryGameSelector(
wolf3d: widget.wolf3d,
selectedGame: _selectedGame,
onSelected: _selectGame,
),
],
),
backgroundColor: Colors.black,
body: GridView.builder(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
@@ -133,7 +94,7 @@ class VgaGallery extends StatelessWidget {
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: images.length,
itemCount: _images.length,
itemBuilder: (context, index) {
return Card(
color: Colors.blueGrey,
@@ -144,15 +105,16 @@ class VgaGallery extends StatelessWidget {
spacing: 8,
children: [
Text(
"Index: $index\n${images[index].width} x ${images[index].height}",
'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]),
aspectRatio:
_images[index].width / _images[index].height,
child: WolfAssetPainter.vga(_images[index]),
),
),
),