Expand Repixelizer parity product surface

This commit is contained in:
Meta Vens 2026-06-05 22:44:18 +01:00
commit be84cd539f
14 changed files with 1102 additions and 3 deletions

View file

@ -46,7 +46,7 @@ class _EveParityAppState extends State<EveParityApp> {
)
: state == null
? const SizedBox.shrink()
: EveSurfaceView(state: state),
: SingleChildScrollView(child: EveSurfaceView(state: state)),
),
),
);
@ -220,6 +220,24 @@ class EveNodeView extends StatelessWidget {
return EveSlider(state: state, node: node);
case 'control.button':
return _button();
case 'image.preview':
return _imagePreview();
case 'canvas.preview':
case 'canvas.editor':
return _canvasPlaceholder(editor: node.kind == 'canvas.editor');
case 'status.stage':
return _statusStage();
case 'input.file':
case 'dropzone':
return _button();
case 'input.number':
case 'input.select':
case 'control.range':
return _field();
case 'control.toggle':
return _toggle();
case 'color.swatch':
return _swatch();
case 'vn.stage':
return _vnStage();
case 'image.background':
@ -449,12 +467,159 @@ class EveNodeView extends StatelessWidget {
);
}
Widget _imagePreview() {
final label = node.props['label']?.toString() ?? 'Image';
final assetPath = _previewAssetPath(node.props['src']);
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: 1,
child: DecoratedBox(
decoration: BoxDecoration(
color: state.tokens.panelInset,
border: Border.all(color: state.tokens.border, width: 2),
),
child: Stack(
children: [
Positioned.fill(child: CustomPaint(painter: EveGridPainter(color: state.tokens.accent.withValues(alpha: 0.10)))),
if (assetPath != null)
Positioned.fill(
child: Image.asset(assetPath, fit: BoxFit.cover, filterQuality: FilterQuality.none),
)
else
Center(
child: Padding(
padding: const EdgeInsets.all(10),
child: Text(
label,
textAlign: TextAlign.center,
style: TextStyle(color: state.tokens.muted, fontFamily: 'EveParity', fontSize: 12),
),
),
),
],
),
),
),
const SizedBox(height: 8),
Text(label, style: TextStyle(color: state.tokens.muted, fontFamily: 'EveParity', fontSize: 12)),
],
);
}
Widget _canvasPlaceholder({required bool editor}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
node.props['label']?.toString() ?? 'Canvas',
style: TextStyle(color: state.tokens.accent, fontFamily: 'EveParity', fontSize: 12, fontWeight: FontWeight.w700),
),
const SizedBox(height: 8),
SizedBox(
height: editor ? 280 : 170,
width: double.infinity,
child: DecoratedBox(
decoration: BoxDecoration(
color: editor ? const Color(0xFF142033) : state.tokens.panelInset,
border: Border.all(color: state.tokens.border, width: 2),
),
child: Stack(
children: [
Positioned.fill(child: CustomPaint(painter: EveGridPainter(color: state.tokens.accent.withValues(alpha: editor ? 0.18 : 0.08)))),
Center(
child: Text(
node.props['state']?.toString() ?? '',
style: TextStyle(color: state.tokens.muted, fontFamily: 'EveParity', fontSize: 12),
),
),
],
),
),
),
],
);
}
Widget _statusStage() {
return DecoratedBox(
decoration: BoxDecoration(
color: state.tokens.panelAlt,
border: Border.all(color: state.tokens.border, width: 2),
borderRadius: BorderRadius.circular(4),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(node.props['label']?.toString() ?? 'Status', style: TextStyle(color: state.tokens.accent, fontFamily: 'EveParity', fontSize: 12, fontWeight: FontWeight.w700)),
const SizedBox(height: 6),
Text(node.props['stage']?.toString() ?? '', style: TextStyle(color: state.tokens.text, fontFamily: 'EveParity', fontSize: 15, fontWeight: FontWeight.w700)),
const SizedBox(height: 4),
Text(node.props['detail']?.toString() ?? '', style: TextStyle(color: state.tokens.muted, fontFamily: 'EveParity', fontSize: 12)),
],
),
),
);
}
Widget _field() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(node.props['label']?.toString() ?? '', style: TextStyle(color: state.tokens.accent, fontFamily: 'EveParity', fontSize: 11, fontWeight: FontWeight.w700)),
const SizedBox(height: 4),
DecoratedBox(
decoration: BoxDecoration(color: state.tokens.panelInset, border: Border.all(color: state.tokens.border)),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
child: Text(node.props['value']?.toString() ?? '', style: TextStyle(color: state.tokens.text, fontFamily: 'EveParity', fontSize: 13)),
),
),
],
);
}
Widget _toggle() {
return DecoratedBox(
decoration: BoxDecoration(color: state.tokens.panelInset, border: Border.all(color: state.tokens.border)),
child: Padding(
padding: const EdgeInsets.all(8),
child: Text(
'${node.props['label'] ?? 'Toggle'}: ${node.props['value'] == true ? 'on' : 'off'}',
style: TextStyle(color: state.tokens.text, fontFamily: 'EveParity', fontSize: 12),
),
),
);
}
Widget _swatch() {
return Container(
width: 38,
height: 38,
decoration: BoxDecoration(
color: _parseColor(node.props['value']) ?? state.tokens.text,
border: Border.all(color: state.tokens.accent, width: 2),
),
);
}
Widget _surface() {
final padding = _number(node.layout['padding'] ?? node.props['padding'], 0);
final direction = node.layout['direction']?.toString();
return Padding(
padding: EdgeInsets.all(padding),
child: LayoutBuilder(
builder: (context, constraints) {
if (direction == 'vertical') {
return _children(axis: Axis.vertical, maxWidth: 1280);
}
if (direction == 'horizontal') {
return _children(axis: Axis.horizontal, maxWidth: 1280);
}
if (constraints.maxWidth >= 900 && node.children.length > 1) {
return _children(axis: Axis.horizontal, maxWidth: 1280);
}
@ -625,6 +790,21 @@ class EveNodeView extends StatelessWidget {
}
Map<String, dynamic> _map(Object? value) => value is Map<String, dynamic> ? value : const {};
Color? _parseColor(Object? value) {
if (value is! String || !value.startsWith('#')) return null;
final body = value.substring(1);
if (body.length == 8) return Color(int.parse(body, radix: 16));
if (body.length == 6) return Color(int.parse('ff$body', radix: 16));
return null;
}
String? _previewAssetPath(Object? source) {
final src = source?.toString() ?? '';
if (src.endsWith('/character-input.png')) return 'assets/repixelizer/character-input.png';
if (src.endsWith('/character-repixelized.png')) return 'assets/repixelizer/character-repixelized.png';
return null;
}
}
class EveGridPainter extends CustomPainter {

View file

@ -53,6 +53,8 @@ flutter:
assets:
- assets/current-surface.json
- assets/repixelizer/character-input.png
- assets/repixelizer/character-repixelized.png
fonts:
- family: EveParity

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 KiB

After

Width:  |  Height:  |  Size: 2.2 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 370 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Before After
Before After

View file

@ -21,6 +21,10 @@ void main() {
await tester.pump(const Duration(milliseconds: 100));
await tester.pumpAndSettle();
await tester.pumpAndSettle();
await tester.runAsync(() async {
await Future<void>.delayed(const Duration(milliseconds: 250));
});
await tester.pumpAndSettle();
expect(find.byType(EveSurfaceView), findsOneWidget);
if (fixtureId == 'cultui-inspector') {
expect(find.text('AETHERIC FIELD TESTER'), findsOneWidget);