Files
mudblock/lib/widgets/button_set_editor.dart
2023-10-15 02:59:47 +03:00

425 lines
11 KiB
Dart

import 'package:flutter/material.dart';
import '../core/features/game_button.dart';
import '../core/features/game_button_set.dart';
import '../dialogs/button_editor_dialog.dart';
class ButtonSetEditor extends StatefulWidget {
const ButtonSetEditor({
super.key,
required this.data,
required this.onUpdate,
});
final GameButtonSetData? data;
final void Function(GameButtonSetData data) onUpdate;
@override
State<ButtonSetEditor> createState() => _ButtonSetEditorState();
}
class _ButtonSetEditorState extends State<ButtonSetEditor> {
late final GameButtonSetData data;
@override
void initState() {
data = widget.data?.copyWith() ?? GameButtonSetData.empty();
super.initState();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return _buildContainer(
context,
(context, index) {
final button = data.buttons[index];
final size = button?.size ?? GameButtonData.defaultSize;
final Widget child = button != null
? FakeGameButton(label: button.label)
: SizedBox.square(
dimension: size,
child: const Icon(Icons.add, color: Colors.white),
);
return Container(
decoration: BoxDecoration(
color: button == null
? theme.dividerColor.withOpacity(0.2)
: theme.dividerColor.withOpacity(0.5),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: theme.dividerColor,
width: 1,
style: BorderStyle.solid,
),
),
child: GameButtonWrapper(
size: size,
isEmpty: button == null,
onAdd: () {
showDialog(
context: context,
builder: (context) => ButtonEditorDialog(
onSave: (added) {
setState(() {
data.buttons[index] = added;
widget.onUpdate(data);
});
},
),
);
},
onEdit: () {
showDialog(
context: context,
builder: (context) => ButtonEditorDialog(
data: button,
onSave: (updated) {
setState(() {
data.buttons[index] = updated;
widget.onUpdate(data);
});
},
),
);
},
onDelete: () {
setState(() {
data.buttons.removeAt(index);
widget.onUpdate(data);
});
},
onClear: () {
setState(() {
data.buttons[index] = null;
widget.onUpdate(data);
});
},
emptySpaceControls: data.type == GameButtonSetType.grid
? _gridMenuItems(index)
: data.type == GameButtonSetType.row
? _rowMenuItems(index)
: _columnMenuItems(index),
child: Padding(
padding: EdgeInsets.all(data.spacing / 2),
child: child,
),
),
);
},
);
}
List<FakeGameButtonMenuItem> _columnMenuItems(int index) {
return [
FakeGameButtonMenuItem(
value: 'add_above',
label: 'Add above',
onSelected: () {
setState(() {
data.buttons.insert(index, null);
widget.onUpdate(data);
});
},
),
FakeGameButtonMenuItem(
value: 'add_below',
label: 'Add below',
onSelected: () {
setState(() {
data.buttons.insert(index + 1, null);
widget.onUpdate(data);
});
},
),
];
}
List<FakeGameButtonMenuItem> _rowMenuItems(int index) {
return [
FakeGameButtonMenuItem(
value: 'add_left',
label: 'Add left',
onSelected: () {
setState(() {
data.buttons.insert(index, null);
widget.onUpdate(data);
});
},
),
FakeGameButtonMenuItem(
value: 'add_right',
label: 'Add right',
onSelected: () {
setState(() {
data.buttons.insert(index + 1, null);
widget.onUpdate(data);
});
},
),
];
}
List<FakeGameButtonMenuItem> _gridMenuItems(int index) {
return [
FakeGameButtonMenuItem(
value: 'add_row_above',
label: 'Add row above',
onSelected: () {
final startOfRowIndex = index - (index % data.colCount);
setState(() {
data.buttons.insertAll(
startOfRowIndex,
List.generate(
data.colCount,
(index) => null,
),
);
widget.onUpdate(data);
});
},
),
FakeGameButtonMenuItem(
value: 'add_row_below',
label: 'Add row below',
onSelected: () {
final rowIndices = data.getRowIndices(
data.getRowFromIndex(index),
);
debugPrint('rowIndices: $rowIndices');
setState(() {
for (final index in rowIndices) {
data.buttons.insert(
index + data.colCount,
null,
);
}
widget.onUpdate(data);
});
},
),
FakeGameButtonMenuItem(
value: 'add_column_left',
label: 'Add column left',
onSelected: () {
final btnIndices = data
.getColumnIndices(
data.getColumnFromIndex(index),
)
.reversed;
debugPrint('btnIndices: $btnIndices');
setState(() {
for (final index in btnIndices) {
data.buttons.insert(index, null);
}
data.crossAxisCount = data.colCount + 1;
widget.onUpdate(data);
});
},
),
FakeGameButtonMenuItem(
value: 'add_column_right',
label: 'Add column right',
onSelected: () {
final btnIndices = data
.getColumnIndices(
data.getColumnFromIndex(index),
)
.reversed;
debugPrint('btnIndices: $btnIndices');
setState(() {
for (final index in btnIndices) {
data.buttons.insert(index + 1, null);
}
data.crossAxisCount = data.colCount + 1;
widget.onUpdate(data);
});
},
),
FakeGameButtonMenuItem(
value: 'remove_row',
label: 'Remove row',
onSelected: () {
final rowIndices = data
.getRowIndices(
data.getRowFromIndex(index),
)
.reversed;
setState(() {
for (final index in rowIndices) {
data.buttons.removeAt(index);
}
widget.onUpdate(data);
});
},
),
FakeGameButtonMenuItem(
value: 'remove_column',
label: 'Remove column',
onSelected: () {
setState(() {
final colIndices = data
.getColumnIndices(
data.getColumnFromIndex(index),
)
.reversed;
for (final index in colIndices) {
data.buttons.removeAt(index);
}
widget.onUpdate(data);
});
},
),
];
}
Widget _buildContainer(
BuildContext context,
Widget Function(BuildContext context, int index) builder,
) {
return Center(
child: Container(
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(16),
),
child: Center(
child: Padding(
padding: const EdgeInsets.all(16),
child: GameButtonSet.buildContainer(
context: context,
type: data.type,
count: data.buttons.length,
crossAxisCount: data.crossAxisCount,
spacing: data.spacing / 4,
alignment: data.alignment,
builder: builder,
),
),
),
),
);
}
}
class GameButtonWrapper extends StatelessWidget {
const GameButtonWrapper({
super.key,
required this.child,
required this.onAdd,
required this.onEdit,
required this.onDelete,
required this.onClear,
required this.size,
required this.isEmpty,
this.emptySpaceControls = const [],
});
final Widget child;
final void Function() onAdd;
final void Function() onEdit;
final void Function() onClear;
final void Function() onDelete;
final double size;
final List<FakeGameButtonMenuItem> emptySpaceControls;
final bool isEmpty;
@override
Widget build(BuildContext context) {
return PopupMenuButton(
offset: Offset(0, size),
tooltip: '',
itemBuilder: (context) => [
if (!isEmpty)
const PopupMenuItem(
value: 'edit',
child: Text('Edit'),
),
if (isEmpty)
const PopupMenuItem(
value: 'add',
child: Text('Add'),
),
...emptySpaceControls.map(
(control) => PopupMenuItem(
value: control.value,
child: Text(control.label),
),
),
if (!isEmpty)
const PopupMenuItem(
value: 'clear',
child: Text('Clear'),
),
const PopupMenuItem(
value: 'delete',
child: Text('Delete'),
),
],
onSelected: (value) {
switch (value) {
case 'add':
onAdd();
case 'edit':
onEdit();
break;
case 'clear':
onClear();
break;
case 'delete':
onDelete();
break;
default:
final control = emptySpaceControls.firstWhere(
(control) => control.value == value,
orElse: () => FakeGameButtonMenuItem(
value: '',
label: '',
onSelected: () {},
),
);
if (control.value.isNotEmpty) {
control.onSelected();
} else {
throw Exception('Unknown value: $value');
}
}
},
child: child,
);
}
}
class FakeGameButton extends StatelessWidget {
const FakeGameButton({
super.key,
required this.label,
});
final GameButtonLabelData label;
@override
Widget build(BuildContext context) {
return GameButton(
enabled: false,
data: GameButtonData.empty().copyWith(label: label),
);
}
}
class FakeGameButtonMenuItem {
final String value;
final String label;
final void Function() onSelected;
const FakeGameButtonMenuItem({
required this.value,
required this.label,
required this.onSelected,
});
}