feat: button editor wip

This commit is contained in:
2023-10-11 10:47:13 +03:00
parent 47a54a20f6
commit 2db3da1438
5 changed files with 345 additions and 120 deletions

15
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
# These are supported funding model platforms
github: chenasraf
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: casraf
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom:
[
'https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=TSH3C3ABGQM22&currency_code=ILS&source=url',
]

View File

@@ -24,9 +24,9 @@ class GameButtonSet extends StatelessWidget {
builder: (context) {
final containerSize = data.size;
return Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.white),
),
// decoration: BoxDecoration(
// border: Border.all(color: Colors.white),
// ),
child: SizedBox(
width: containerSize.width,
height: containerSize.height,
@@ -193,27 +193,13 @@ class GameButtonSetData {
double calculateWidth() {
switch (type) {
case GameButtonSetType.row:
return buttons.fold<double>(
0,
(sum, button) =>
sum + (button?.size ?? GameButtonData.defaultSize)) +
(buttons.length - 1) * spacing;
return sumSize(buttons) + (buttons.length - 1) * spacing;
case GameButtonSetType.column:
return buttons.fold<double>(
0,
(sum, button) =>
max(sum, button?.size ?? GameButtonData.defaultSize)) +
(buttons.length - 1) * spacing;
return maxSize(buttons) + spacing;
case GameButtonSetType.grid:
final rowCount = buttons.length ~/ (crossAxisCount ?? 3);
final colCount = buttons.length ~/ rowCount;
final colWidths = List.generate(
colCount,
(index) => getButtonsInColumn(index).fold<double>(
0,
(sum, button) =>
max(sum, button?.size ?? GameButtonData.defaultSize),
),
(index) => maxSize(getColumnButtons(index)),
);
return colWidths.fold<double>(0, (sum, width) => sum + width) +
@@ -224,46 +210,71 @@ class GameButtonSetData {
double calculateHeight() {
switch (type) {
case GameButtonSetType.row:
return buttons.fold<double>(
0,
(sum, button) =>
max(sum, button?.size ?? GameButtonData.defaultSize)) +
(buttons.length - 1) * spacing;
return maxSize(buttons) + spacing;
case GameButtonSetType.column:
return buttons.fold<double>(
0,
(sum, button) =>
sum + (button?.size ?? GameButtonData.defaultSize)) +
(buttons.length - 1) * spacing;
return sumSize(buttons) + (buttons.length - 1) * spacing;
case GameButtonSetType.grid:
final rowCount = buttons.length ~/ (crossAxisCount ?? 3);
final rowHeights = List.generate(
rowCount,
(index) => getButtonsInRow(index).fold<double>(
0,
(sum, button) =>
max(sum, button?.size ?? GameButtonData.defaultSize),
),
(index) => maxSize(getRowButtons(index)),
);
return rowHeights.fold<double>(0, (sum, height) => sum + height) +
(rowCount - 1) * spacing;
}
}
List<GameButtonData?> getButtonsInRow(int row) {
final rowCount = buttons.length ~/ (crossAxisCount ?? 3);
final colCount = buttons.length ~/ rowCount;
return buttons.sublist(row * colCount, (row + 1) * colCount).toList();
}
List<GameButtonData?> getButtonsInColumn(int col) {
final rowCount = buttons.length ~/ (crossAxisCount ?? 3);
final colCount = buttons.length ~/ rowCount;
return List<GameButtonData?>.generate(
rowCount,
(index) => buttons[index * colCount + col],
double maxSize(List<GameButtonData?> buttons) {
return buttons.fold<double>(
0,
(sum, button) => max(sum, button?.size ?? GameButtonData.defaultSize),
);
}
double sumSize(List<GameButtonData?> buttons) {
return buttons.fold<double>(
0,
(sum, button) => sum + (button?.size ?? GameButtonData.defaultSize),
);
}
List<GameButtonData?> getRowButtons(int row) =>
getRowIndices(row).map((index) => buttons[index]).toList(growable: false);
List<GameButtonData?> getColumnButtons(int col) => getColumnIndices(col)
.map((index) => buttons[index])
.toList(growable: false);
int get rowCount {
switch (type) {
case GameButtonSetType.row:
return 1;
case GameButtonSetType.column:
return buttons.length;
case GameButtonSetType.grid:
return buttons.length ~/ crossAxisCount!;
}
}
int get colCount {
switch (type) {
case GameButtonSetType.row:
return buttons.length;
case GameButtonSetType.column:
return 1;
case GameButtonSetType.grid:
return crossAxisCount!;
}
}
List<int> getRowIndices(int row) =>
List<int>.generate(colCount, (index) => row * colCount + index);
List<int> getColumnIndices(int col) =>
List<int>.generate(rowCount, (index) => index * colCount + col);
int getColumnFromIndex(int index) => index % colCount;
int getRowFromIndex(int index) => index ~/ colCount;
}
enum GameButtonSetType {

View File

@@ -26,6 +26,7 @@ void main() async {
backgroundColor: Colors.transparent,
skipTaskbar: false,
titleBarStyle: TitleBarStyle.hidden,
center: true,
);
windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.show();
@@ -66,3 +67,4 @@ class MyApp extends StatelessWidget {
);
}
}

View File

@@ -29,14 +29,19 @@ class ButtonSetListPage extends StatelessWidget with GameStoreMixin {
),
];
},
onSelected: (value) {
onSelected: (value) async {
final store = storeOf(context);
switch (value) {
case 'navigation_preset':
Navigator.pushNamed(
final data = await Navigator.pushNamed(
context,
Paths.buttonSet,
arguments: movementPreset,
);
if (data != null) {
store.currentProfile.saveButtonSet(data as GameButtonSetData);
store.loadButtonSets();
}
break;
}
},
@@ -93,3 +98,4 @@ class ButtonSetListPage extends StatelessWidget with GameStoreMixin {
await store.loadButtonSets();
}
}

View File

@@ -33,76 +33,231 @@ class _ButtonSetEditorState extends State<ButtonSetEditor> {
context,
(context, index) {
final button = data.buttons[index];
final size = button?.size ?? GameButtonData.defaultSize;
final Widget child = button != null
? FakeGameButton(label: button.label)
: const Icon(Icons.add);
return Container(
height: (button?.size ?? GameButtonData.defaultSize) - data.spacing,
width: (button?.size ?? GameButtonData.defaultSize) - data.spacing,
height: size - data.spacing,
width: size - data.spacing,
color: Colors.grey,
child: button != null
? FakeGameButton(
label: button.label,
size: button.size ?? GameButtonData.defaultSize,
spacing: data.spacing,
onEdit: () {
showDialog(
context: context,
builder: (context) => ButtonEditorDialog(
data: button,
onSave: (data) {
setState(() {
this.data.buttons[index] = data;
});
},
),
);
},
onDelete: () {
child: GameButtonWrapper(
size: size,
isEmpty: button == null,
spacing: data.spacing,
onAdd: () {
showDialog(
context: context,
builder: (context) => ButtonEditorDialog(
onSave: (data) {
setState(() {
data.buttons[index] = null;
this.data.buttons[index] = data;
});
},
emptySpaceControls: [
FakeGameButtonMenuItem(
value: 'add_row_above',
label: 'Add row above',
onSelected: () {
final colCount = data.crossAxisCount ?? 3;
final startOfRowIndex = index - (index % colCount);
setState(() {
data.buttons.insertAll(
startOfRowIndex,
List.generate(
colCount,
(index) => null,
),
);
widget.onUpdate(data);
});
},
),
],
)
: IconButton(
icon: const Icon(Icons.add),
onPressed: () {
showDialog(
context: context,
builder: (context) => ButtonEditorDialog(
onSave: (data) {
setState(() {
this.data.buttons[index] = data;
widget.onUpdate(this.data);
});
},
),
);
),
);
},
onEdit: () {
showDialog(
context: context,
builder: (context) => ButtonEditorDialog(
data: button,
onSave: (data) {
setState(() {
this.data.buttons[index] = data;
});
},
),
);
},
onDelete: () {
setState(() {
data.buttons.removeAt(index);
});
},
onClear: () {
setState(() {
data.buttons[index] = null;
});
},
emptySpaceControls: data.type == GameButtonSetType.grid
? _gridMenuItems(index)
: data.type == GameButtonSetType.row
? _rowMenuItems(index)
: _columnMenuItems(index),
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),
)
.reversed;
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,
@@ -127,23 +282,29 @@ class _ButtonSetEditorState extends State<ButtonSetEditor> {
}
}
class FakeGameButton extends StatelessWidget {
const FakeGameButton({
class GameButtonWrapper extends StatelessWidget {
const GameButtonWrapper({
super.key,
required this.label,
required this.child,
required this.onAdd,
required this.onEdit,
required this.onDelete,
required this.onClear,
required this.size,
required this.spacing,
required this.isEmpty,
this.emptySpaceControls = const [],
});
final GameButtonLabelData label;
final Widget child;
final void Function() onAdd;
final void Function() onEdit;
final void Function() onClear;
final void Function() onDelete;
final double size;
final double spacing;
final List<FakeGameButtonMenuItem> emptySpaceControls;
final bool isEmpty;
@override
Widget build(BuildContext context) {
@@ -151,16 +312,27 @@ class FakeGameButton extends StatelessWidget {
offset: Offset(0, size),
tooltip: '',
itemBuilder: (context) => [
const PopupMenuItem(
value: 'edit',
child: Text('Edit'),
),
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'),
@@ -168,9 +340,14 @@ class FakeGameButton extends StatelessWidget {
],
onSelected: (value) {
switch (value) {
case 'add':
onAdd();
case 'edit':
onEdit();
break;
case 'clear':
onClear();
break;
case 'delete':
onDelete();
break;
@@ -190,10 +367,24 @@ class FakeGameButton extends StatelessWidget {
}
}
},
child: GameButton(
enabled: false,
data: GameButtonData.empty().copyWith(label: label),
),
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),
);
}
}