mirror of
https://github.com/chenasraf/mudblock.git
synced 2026-05-18 01:48:57 +00:00
feat: button label editor updates
This commit is contained in:
@@ -43,3 +43,4 @@ class DialogButtonSet {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,9 @@ class MUDAction {
|
||||
debugPrint('MUDAction.invoke: ${this.content}, $matches');
|
||||
var content = this.content;
|
||||
for (var i = 0; i < matches.length; i++) {
|
||||
content = content.replaceAll('%$i', matches[i]);
|
||||
// escape %% in the pattern
|
||||
final pattern = RegExp('(?<!%)%$i');
|
||||
content = content.replaceAll(pattern, matches[i]);
|
||||
}
|
||||
content = doVariableReplacements(store, content);
|
||||
debugPrint('MUDAction.invoking: $content');
|
||||
|
||||
@@ -228,6 +228,11 @@ class GameButtonData {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'GameButtonData(id: $id, label: $label)';
|
||||
}
|
||||
}
|
||||
|
||||
class GameButton extends StatefulWidget {
|
||||
@@ -518,10 +523,14 @@ class GameButtonLabel extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconTheme(
|
||||
data: _iconTheme(context),
|
||||
child: Icon(data.icon),
|
||||
);
|
||||
if (data.isText) {
|
||||
return Center(child: Text(data.label!, textScaleFactor: 1.2));
|
||||
} else {
|
||||
return IconTheme(
|
||||
data: _iconTheme(context),
|
||||
child: Icon(data.icon),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
IconThemeData _iconTheme(BuildContext context) =>
|
||||
@@ -531,15 +540,21 @@ class GameButtonLabel extends StatelessWidget {
|
||||
class GameButtonLabelData {
|
||||
String? label;
|
||||
IconData? icon;
|
||||
String? iconName;
|
||||
IconThemeData? iconTheme;
|
||||
|
||||
bool get isIcon => icon != null;
|
||||
bool get isText => label != null;
|
||||
|
||||
GameButtonLabelData({
|
||||
this.label,
|
||||
this.icon,
|
||||
this.iconName,
|
||||
this.iconTheme,
|
||||
}) : assert(label != null || icon != null);
|
||||
}) : assert(label != null || icon != null),
|
||||
assert(icon == null || iconName != null);
|
||||
|
||||
factory GameButtonLabelData.empty() => GameButtonLabelData(label: '?');
|
||||
factory GameButtonLabelData.empty() => GameButtonLabelData(label: '');
|
||||
|
||||
factory GameButtonLabelData.fromJson(Map<String, dynamic> json) {
|
||||
return GameButtonLabelData(
|
||||
@@ -550,6 +565,7 @@ class GameButtonLabelData {
|
||||
fontFamily: json['iconFontFamily'] ?? 'MaterialIcons',
|
||||
)
|
||||
: null,
|
||||
iconName: json['iconName'] ?? '',
|
||||
iconTheme: json['iconTheme'] != null
|
||||
? _iconThemeDataFromJson(json['iconTheme'])
|
||||
: null,
|
||||
@@ -560,6 +576,7 @@ class GameButtonLabelData {
|
||||
return {
|
||||
'label': label,
|
||||
'icon': icon?.codePoint,
|
||||
'iconName': iconName,
|
||||
'iconFontFamily': icon?.fontFamily,
|
||||
'iconTheme': iconTheme != null ? _iconThemeDataToJson(iconTheme!) : null,
|
||||
};
|
||||
@@ -581,14 +598,23 @@ class GameButtonLabelData {
|
||||
GameButtonLabelData copyWith({
|
||||
String? label,
|
||||
IconData? icon,
|
||||
String? iconName,
|
||||
IconThemeData? iconTheme,
|
||||
}) {
|
||||
label = icon != null ? null : label ?? this.label;
|
||||
icon = label != null ? null : icon ?? this.icon;
|
||||
return GameButtonLabelData(
|
||||
label: label ?? this.label,
|
||||
icon: icon ?? this.icon,
|
||||
iconName: iconName ?? this.iconName,
|
||||
iconTheme: iconTheme ?? this.iconTheme,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'GameButtonLabel(${isText ? 'label:' : 'icon:'} ${isText ? label! : iconName!})';
|
||||
}
|
||||
}
|
||||
|
||||
enum GameButtonInteraction {
|
||||
|
||||
@@ -191,10 +191,12 @@ class GameButtonSetData {
|
||||
Alignment? alignment,
|
||||
double? spacing,
|
||||
String? group,
|
||||
GameButtonLabelData? label,
|
||||
}) =>
|
||||
GameButtonSetData(
|
||||
id: id ?? this.id,
|
||||
enabled: enabled ?? this.enabled,
|
||||
label: label?.label ?? this.label,
|
||||
name: name ?? this.name,
|
||||
type: type ?? this.type,
|
||||
buttons: buttons ?? [...this.buttons],
|
||||
@@ -307,42 +309,69 @@ final movementPreset = GameButtonSetData(
|
||||
null,
|
||||
GameButtonData(
|
||||
id: uuid(),
|
||||
label: GameButtonLabelData(icon: Icons.keyboard_arrow_up),
|
||||
label: GameButtonLabelData(
|
||||
icon: Icons.keyboard_arrow_up,
|
||||
iconName: 'keyboard_arrow_up',
|
||||
),
|
||||
pressAction: MUDAction('north'),
|
||||
),
|
||||
GameButtonData(
|
||||
id: uuid(),
|
||||
label: GameButtonLabelData(icon: Icons.keyboard_double_arrow_up),
|
||||
label: GameButtonLabelData(
|
||||
icon: Icons.keyboard_double_arrow_up,
|
||||
iconName: 'keyboard_double_arrow_up',
|
||||
),
|
||||
pressAction: MUDAction('up'),
|
||||
),
|
||||
GameButtonData(
|
||||
id: uuid(),
|
||||
label: GameButtonLabelData(icon: Icons.keyboard_arrow_left),
|
||||
label: GameButtonLabelData(
|
||||
icon: Icons.keyboard_arrow_left,
|
||||
iconName: 'keyboard_arrow_left',
|
||||
),
|
||||
pressAction: MUDAction('west'),
|
||||
),
|
||||
GameButtonData(
|
||||
id: uuid(),
|
||||
label: GameButtonLabelData(icon: Icons.visibility_outlined),
|
||||
labelUp: GameButtonLabelData(icon: Icons.exit_to_app),
|
||||
labelDown: GameButtonLabelData(icon: Icons.exit_to_app),
|
||||
label: GameButtonLabelData(
|
||||
icon: Icons.visibility_outlined,
|
||||
iconName: 'visibility_outlined',
|
||||
),
|
||||
labelUp: GameButtonLabelData(
|
||||
icon: Icons.exit_to_app,
|
||||
iconName: 'exit_to_app',
|
||||
),
|
||||
labelDown: GameButtonLabelData(
|
||||
icon: Icons.exit_to_app,
|
||||
iconName: 'exit_to_app',
|
||||
),
|
||||
pressAction: MUDAction('look'),
|
||||
dragUpAction: MUDAction('exits'),
|
||||
dragDownAction: MUDAction('exits'),
|
||||
),
|
||||
GameButtonData(
|
||||
id: uuid(),
|
||||
label: GameButtonLabelData(icon: Icons.keyboard_arrow_right),
|
||||
label: GameButtonLabelData(
|
||||
icon: Icons.keyboard_arrow_right,
|
||||
iconName: 'keyboard_arrow_right',
|
||||
),
|
||||
pressAction: MUDAction('east'),
|
||||
),
|
||||
null,
|
||||
GameButtonData(
|
||||
id: uuid(),
|
||||
label: GameButtonLabelData(icon: Icons.keyboard_arrow_down),
|
||||
label: GameButtonLabelData(
|
||||
icon: Icons.keyboard_arrow_down,
|
||||
iconName: 'keyboard_arrow_down',
|
||||
),
|
||||
pressAction: MUDAction('south'),
|
||||
),
|
||||
GameButtonData(
|
||||
id: uuid(),
|
||||
label: GameButtonLabelData(icon: Icons.keyboard_double_arrow_down),
|
||||
label: GameButtonLabelData(
|
||||
icon: Icons.keyboard_double_arrow_down,
|
||||
iconName: 'keyboard_double_arrow_down',
|
||||
),
|
||||
pressAction: MUDAction('down'),
|
||||
),
|
||||
],
|
||||
|
||||
3
lib/core/map_utils.dart
Normal file
3
lib/core/map_utils.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
extension MapExtension<K, V> on Map<K, V> {
|
||||
Map<V, K> get inverse => map((k, v) => MapEntry(v, k));
|
||||
}
|
||||
@@ -87,7 +87,7 @@ class _ButtonEditorDialogState extends State<ButtonEditorDialog> {
|
||||
GameButtonInteraction.longPress
|
||||
? null
|
||||
: () async {
|
||||
final icon = await showDialog(
|
||||
final updated = await showDialog(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
GameButtonLabelEditorDialog(
|
||||
@@ -95,11 +95,9 @@ class _ButtonEditorDialogState extends State<ButtonEditorDialog> {
|
||||
label ?? GameButtonLabelData.empty(),
|
||||
),
|
||||
);
|
||||
if (icon != null) {
|
||||
final data = label?.copyWith(icon: icon) ??
|
||||
GameButtonLabelData(icon: icon);
|
||||
if (updated != null) {
|
||||
setState(() {
|
||||
this.data.setLabel(interaction, data);
|
||||
data.setLabel(interaction, updated);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../core/dialog_utils.dart';
|
||||
import '../core/features/game_button.dart';
|
||||
import '../widgets/icon_selector.dart';
|
||||
|
||||
@@ -14,12 +15,38 @@ class GameButtonLabelEditorDialog extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _GameButtonLabelEditorDialogState
|
||||
extends State<GameButtonLabelEditorDialog> {
|
||||
String search = '';
|
||||
extends State<GameButtonLabelEditorDialog> with TickerProviderStateMixin {
|
||||
IconSelectorDisplay display = IconSelectorDisplay.grid;
|
||||
late final GameButtonLabelData data;
|
||||
late String search;
|
||||
late int tabIndex;
|
||||
late TabController tabController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
data = widget.data.copyWith();
|
||||
debugPrint('data: ${data.iconName}');
|
||||
search = data.iconName ?? '';
|
||||
tabIndex = data.icon == null && data.label != null && data.label!.isNotEmpty
|
||||
? 1
|
||||
: 0;
|
||||
tabController =
|
||||
TabController(vsync: this, length: tabs.length, initialIndex: tabIndex);
|
||||
}
|
||||
|
||||
final tabs = [
|
||||
const Tab(text: 'Icon'),
|
||||
const Tab(text: 'Text/Emoji'),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final actions = DialogUtils.saveButtons(
|
||||
context,
|
||||
() => Navigator.pop(context, data),
|
||||
dismissOnSave: false,
|
||||
);
|
||||
return Dialog(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
@@ -33,40 +60,97 @@ class _GameButtonLabelEditorDialogState
|
||||
'Edit Label',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
onChanged: (value) => setState(() => search = value),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Search Icon',
|
||||
const SizedBox(height: 16),
|
||||
TabBar(
|
||||
controller: tabController,
|
||||
tabs: tabs,
|
||||
),
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
// height: tabIndex == 0 ? 400 : 100,
|
||||
height: 400,
|
||||
child: TabBarView(
|
||||
controller: tabController,
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
initialValue: search,
|
||||
onChanged: (value) =>
|
||||
setState(() => search = value),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Search Icon',
|
||||
suffixIcon: search.isNotEmpty
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () =>
|
||||
setState(() => search = ''),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
display == IconSelectorDisplay.grid
|
||||
? Icons.view_list
|
||||
: Icons.view_module,
|
||||
),
|
||||
onPressed: () => setState(
|
||||
() => display =
|
||||
display == IconSelectorDisplay.grid
|
||||
? IconSelectorDisplay.list
|
||||
: IconSelectorDisplay.grid,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
height: 600,
|
||||
child: IconSelector(
|
||||
search: search,
|
||||
display: display,
|
||||
selectedIcon: data.icon,
|
||||
onSelected: (icon, iconName) {
|
||||
setState(() {
|
||||
data.icon = icon;
|
||||
data.iconName = iconName;
|
||||
data.label = null;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
display == IconSelectorDisplay.grid
|
||||
? Icons.view_list
|
||||
: Icons.view_module,
|
||||
TextFormField(
|
||||
initialValue: data.label,
|
||||
onChanged: (value) => setState(
|
||||
() {
|
||||
setState(() {
|
||||
data.label = value;
|
||||
data.icon = null;
|
||||
data.iconName = null;
|
||||
});
|
||||
},
|
||||
),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Label',
|
||||
helperText:
|
||||
'You can use plain text or emojies to be shown on the button',
|
||||
),
|
||||
),
|
||||
onPressed: () => setState(() => display =
|
||||
display == IconSelectorDisplay.grid
|
||||
? IconSelectorDisplay.list
|
||||
: IconSelectorDisplay.grid),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
height: 600,
|
||||
child: IconSelector(
|
||||
search: search,
|
||||
display: display,
|
||||
onSelected: (icon) {
|
||||
Navigator.of(context).pop(icon);
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
actions.row(),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -75,3 +159,4 @@ class _GameButtonLabelEditorDialogState
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -188,8 +188,6 @@ class _ButtonSetEditorState extends State<ButtonSetEditor> {
|
||||
data.getRowFromIndex(index),
|
||||
);
|
||||
|
||||
debugPrint('rowIndices: $rowIndices');
|
||||
|
||||
setState(() {
|
||||
for (final index in rowIndices) {
|
||||
data.buttons.insert(
|
||||
@@ -210,7 +208,6 @@ class _ButtonSetEditorState extends State<ButtonSetEditor> {
|
||||
data.getColumnFromIndex(index),
|
||||
)
|
||||
.reversed;
|
||||
debugPrint('btnIndices: $btnIndices');
|
||||
setState(() {
|
||||
for (final index in btnIndices) {
|
||||
data.buttons.insert(index, null);
|
||||
@@ -229,7 +226,6 @@ class _ButtonSetEditorState extends State<ButtonSetEditor> {
|
||||
data.getColumnFromIndex(index),
|
||||
)
|
||||
.reversed;
|
||||
debugPrint('btnIndices: $btnIndices');
|
||||
setState(() {
|
||||
for (final index in btnIndices) {
|
||||
data.buttons.insert(index + 1, null);
|
||||
|
||||
@@ -10,12 +10,14 @@ class IconSelector extends StatelessWidget {
|
||||
super.key,
|
||||
this.search = '',
|
||||
required this.onSelected,
|
||||
this.selectedIcon,
|
||||
this.display = IconSelectorDisplay.grid,
|
||||
});
|
||||
|
||||
final ValueChanged<IconData> onSelected;
|
||||
final void Function(IconData data, String name) onSelected;
|
||||
final String search;
|
||||
final IconSelectorDisplay display;
|
||||
final IconData? selectedIcon;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -56,21 +58,40 @@ class IconSelector extends StatelessWidget {
|
||||
leading: Icon(e.value),
|
||||
title: Text(e.key, overflow: TextOverflow.ellipsis),
|
||||
subtitle: Text(e.key),
|
||||
onTap: () => onSelected(e.value),
|
||||
selected: selectedIcon != null && selectedIcon == e.value,
|
||||
onTap: () => onSelected(e.value, e.key),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGridItem(BuildContext context, MapEntry<String, IconData> e) {
|
||||
final radius = BorderRadius.circular(16);
|
||||
return InkWell(
|
||||
onTap: () => onSelected(e.value),
|
||||
child: Tooltip(
|
||||
message: e.key,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(e.value),
|
||||
Text(e.key, overflow: TextOverflow.ellipsis),
|
||||
],
|
||||
onTap: () => onSelected(e.value, e.key),
|
||||
borderRadius: radius,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: radius,
|
||||
color: selectedIcon != null && selectedIcon == e.value
|
||||
? Theme.of(context).primaryColor.withOpacity(0.1)
|
||||
: null,
|
||||
),
|
||||
child: Tooltip(
|
||||
message: e.key,
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(e.value),
|
||||
Text(e.key,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 3,
|
||||
textAlign: TextAlign.center),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -82,6 +103,12 @@ class IconSelector extends StatelessWidget {
|
||||
// // key = key.replaceAll('_sharp', '').replaceAll('_outline', '');
|
||||
// return false;
|
||||
// }
|
||||
return search.isEmpty || key.contains(search.toLowerCase());
|
||||
return search.isEmpty ||
|
||||
_cleanString(key).contains(_cleanString(search.toLowerCase()));
|
||||
}
|
||||
|
||||
String _cleanString(String s) {
|
||||
return s.replaceAll('_', '').replaceAll('-', '');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user