feat: update button sets, split outgoing by ;

This commit is contained in:
2023-10-06 17:26:37 +03:00
parent 1ec3a84a15
commit e45236de4e
7 changed files with 253 additions and 128 deletions

View File

@@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
class DialogUtils {
static DialogButtonSet saveButtons(
BuildContext context,
Function() onSave, {
bool dismissOnSave = true,
}) {
return DialogButtonSet(
confirm: ElevatedButton(
child: const Text('Save'),
onPressed: () {
onSave();
if (dismissOnSave) {
Navigator.of(context).pop();
}
},
),
dismiss: TextButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
);
}
}
class DialogButtonSet {
final Widget confirm;
final Widget dismiss;
DialogButtonSet({required this.confirm, required this.dismiss});
Widget row() {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
dismiss,
const SizedBox(width: 8),
confirm,
],
);
}
}

View File

@@ -15,7 +15,7 @@ enum MUDActionTarget {
class MUDAction {
String content;
MUDActionTarget target;
MUDAction(this.content, {this.target = MUDActionTarget.world});
MUDAction(this.content, {this.target = MUDActionTarget.execute});
void invoke(GameStore store, Automation parent, List<String> matches) {
debugPrint('MUDAction.invoke: ${this.content}, $matches');

View File

@@ -7,7 +7,7 @@ import 'action.dart';
import 'automation.dart';
class GameButtonData {
const GameButtonData({
GameButtonData({
required this.id,
required this.label,
required this.pressAction,
@@ -18,28 +18,28 @@ class GameButtonData {
this.color,
this.size = defaultSize,
this.longPressAction,
this.swipeUpAction,
this.swipeDownAction,
this.swipeLeftAction,
this.swipeRightAction,
this.dragUpAction,
this.dragDownAction,
this.dragLeftAction,
this.dragRightAction,
});
static const defaultSize = 60.0;
final String id;
final GameButtonLabelData label;
final GameButtonLabelData? labelUp;
final GameButtonLabelData? labelDown;
final GameButtonLabelData? labelLeft;
final GameButtonLabelData? labelRight;
final Color? color;
final double? size;
final MUDAction pressAction;
final MUDAction? longPressAction;
final MUDAction? swipeUpAction;
final MUDAction? swipeDownAction;
final MUDAction? swipeLeftAction;
final MUDAction? swipeRightAction;
GameButtonLabelData label;
GameButtonLabelData? labelUp;
GameButtonLabelData? labelDown;
GameButtonLabelData? labelLeft;
GameButtonLabelData? labelRight;
Color? color;
double? size;
MUDAction pressAction;
MUDAction? longPressAction;
MUDAction? dragUpAction;
MUDAction? dragDownAction;
MUDAction? dragLeftAction;
MUDAction? dragRightAction;
factory GameButtonData.empty() => GameButtonData(
id: uuid(),
@@ -69,18 +69,18 @@ class GameButtonData {
longPressAction: json['longPressAction'] == null
? null
: MUDAction.fromJson(json['longPressAction']),
swipeUpAction: json['swipeUpAction'] == null
dragUpAction: json['dragUpAction'] == null
? null
: MUDAction.fromJson(json['swipeUpAction']),
swipeDownAction: json['swipeDownAction'] == null
: MUDAction.fromJson(json['dragUpAction']),
dragDownAction: json['dragDownAction'] == null
? null
: MUDAction.fromJson(json['swipeDownAction']),
swipeLeftAction: json['swipeLeftAction'] == null
: MUDAction.fromJson(json['dragDownAction']),
dragLeftAction: json['dragLeftAction'] == null
? null
: MUDAction.fromJson(json['swipeLeftAction']),
swipeRightAction: json['swipeRightAction'] == null
: MUDAction.fromJson(json['dragLeftAction']),
dragRightAction: json['dragRightAction'] == null
? null
: MUDAction.fromJson(json['swipeRightAction']),
: MUDAction.fromJson(json['dragRightAction']),
);
}
@@ -95,10 +95,10 @@ class GameButtonData {
double? size,
MUDAction? pressAction,
MUDAction? longPressAction,
MUDAction? swipeUpAction,
MUDAction? swipeDownAction,
MUDAction? swipeLeftAction,
MUDAction? swipeRightAction,
MUDAction? dragUpAction,
MUDAction? dragDownAction,
MUDAction? dragLeftAction,
MUDAction? dragRightAction,
}) =>
GameButtonData(
id: id ?? this.id,
@@ -111,10 +111,10 @@ class GameButtonData {
size: size ?? this.size,
pressAction: pressAction ?? this.pressAction,
longPressAction: longPressAction ?? this.longPressAction,
swipeUpAction: swipeUpAction ?? this.swipeUpAction,
swipeDownAction: swipeDownAction ?? this.swipeDownAction,
swipeLeftAction: swipeLeftAction ?? this.swipeLeftAction,
swipeRightAction: swipeRightAction ?? this.swipeRightAction,
dragUpAction: dragUpAction ?? this.dragUpAction,
dragDownAction: dragDownAction ?? this.dragDownAction,
dragLeftAction: dragLeftAction ?? this.dragLeftAction,
dragRightAction: dragRightAction ?? this.dragRightAction,
);
Map<String, dynamic> toJson() => {
@@ -128,41 +128,55 @@ class GameButtonData {
'color': color?.value,
'size': size,
'longPressAction': longPressAction?.toJson(),
'swipeUpAction': swipeUpAction?.toJson(),
'swipeDownAction': swipeDownAction?.toJson(),
'swipeLeftAction': swipeLeftAction?.toJson(),
'swipeRightAction': swipeRightAction?.toJson(),
'dragUpAction': dragUpAction?.toJson(),
'dragDownAction': dragDownAction?.toJson(),
'dragLeftAction': dragLeftAction?.toJson(),
'dragRightAction': dragRightAction?.toJson(),
};
MUDAction directionalAction(GameButtonDirection direction) {
switch (direction) {
case GameButtonDirection.up:
return swipeUpAction ?? pressAction;
case GameButtonDirection.down:
return swipeDownAction ?? pressAction;
case GameButtonDirection.left:
return swipeLeftAction ?? pressAction;
case GameButtonDirection.right:
return swipeRightAction ?? pressAction;
default:
MUDAction getActionOrDefault(GameButtonInteraction interaction) {
return getAction(interaction) ?? pressAction;
}
MUDAction? getAction(GameButtonInteraction interaction) {
switch (interaction) {
case GameButtonInteraction.press:
return pressAction;
case GameButtonInteraction.longPress:
return longPressAction;
case GameButtonInteraction.dragUp:
return dragUpAction;
case GameButtonInteraction.dragDown:
return dragDownAction;
case GameButtonInteraction.dragLeft:
return dragLeftAction;
case GameButtonInteraction.dragRight:
return dragRightAction;
default:
return null;
}
}
MUDAction? actionForDirection(GameButtonDirection direction) {
void setAction(GameButtonInteraction direction, MUDAction mudAction) {
switch (direction) {
case GameButtonDirection.none:
return pressAction;
case GameButtonDirection.up:
return swipeUpAction;
case GameButtonDirection.down:
return swipeDownAction;
case GameButtonDirection.left:
return swipeLeftAction;
case GameButtonDirection.right:
return swipeRightAction;
default:
return null;
case GameButtonInteraction.press:
pressAction = mudAction;
break;
case GameButtonInteraction.longPress:
longPressAction = mudAction;
break;
case GameButtonInteraction.dragUp:
dragUpAction = mudAction;
break;
case GameButtonInteraction.dragDown:
dragDownAction = mudAction;
break;
case GameButtonInteraction.dragLeft:
dragLeftAction = mudAction;
break;
case GameButtonInteraction.dragRight:
dragRightAction = mudAction;
break;
}
}
}
@@ -180,7 +194,7 @@ class GameButton extends StatefulWidget {
}
class _GameButtonState extends State<GameButton> with GameStoreStateMixin {
late GameButtonDirection _direction;
late GameButtonInteraction _direction;
final parentAutomation = Automation.empty();
Offset? _dragStart;
Offset? _dragEnd;
@@ -190,7 +204,7 @@ class _GameButtonState extends State<GameButton> with GameStoreStateMixin {
@override
void initState() {
_direction = GameButtonDirection.none;
_direction = GameButtonInteraction.press;
super.initState();
}
@@ -214,7 +228,7 @@ class _GameButtonState extends State<GameButton> with GameStoreStateMixin {
);
}
double get _swipeMinDist =>
double get _dragMinDist =>
(data.size ??
IconTheme.of(context).size ??
const IconThemeData.fallback().size!) /
@@ -248,13 +262,13 @@ class _GameButtonState extends State<GameButton> with GameStoreStateMixin {
GameButtonLabelData _currentDirectionIcon(BuildContext context) {
switch (_direction) {
case GameButtonDirection.up:
case GameButtonInteraction.dragUp:
return data.labelUp ?? data.label;
case GameButtonDirection.down:
case GameButtonInteraction.dragDown:
return data.labelDown ?? data.label;
case GameButtonDirection.left:
case GameButtonInteraction.dragLeft:
return data.labelLeft ?? data.label;
case GameButtonDirection.right:
case GameButtonInteraction.dragRight:
return data.labelRight ?? data.label;
default:
return data.label;
@@ -280,17 +294,17 @@ class _GameButtonState extends State<GameButton> with GameStoreStateMixin {
void _onDragStart(DragStartDetails details) {
_dragStart = details.globalPosition;
setState(() {
_direction = GameButtonDirection.none;
_direction = GameButtonInteraction.press;
});
}
void _onDragUpdate(DragUpdateDetails details) {
final pos = details.globalPosition;
final direction = _getRelativeDragDirection(_dragStart!, pos);
final interaction = _getRelativeDragDirection(_dragStart!, pos);
if (direction != _direction) {
if (interaction != _direction) {
setState(() {
_direction = direction;
_direction = interaction;
});
}
}
@@ -298,70 +312,71 @@ class _GameButtonState extends State<GameButton> with GameStoreStateMixin {
void _onDragEnd(DragEndDetails details) {
_callCurrentDirection();
setState(() {
_direction = GameButtonDirection.none;
_direction = GameButtonInteraction.press;
});
}
void _onPointerUp(PointerUpEvent event) {
_callCurrentDirection();
setState(() {
_direction = GameButtonDirection.none;
_direction = GameButtonInteraction.press;
});
}
void _onPointerDown(PointerDownEvent event) {
_dragStart = event.position;
setState(() {
_direction = GameButtonDirection.none;
_direction = GameButtonInteraction.press;
});
}
void _onPointerMove(PointerMoveEvent event) {
_dragEnd = event.position;
final direction = _getRelativeDragDirection(_dragStart!, _dragEnd!);
final interaction = _getRelativeDragDirection(_dragStart!, _dragEnd!);
if (direction != _direction) {
if (interaction != _direction) {
setState(() {
_direction = direction;
_direction = interaction;
});
}
}
GameButtonDirection _getRelativeDragDirection(Offset start, Offset current) {
GameButtonInteraction _getRelativeDragDirection(
Offset start, Offset current) {
final diff = start - current;
GameButtonDirection direction = _direction;
GameButtonInteraction interaction = _direction;
if (diff.distance > _swipeMinDist) {
// detect primary direction
if (diff.distance > _dragMinDist) {
// detect primary interaction
// horizontal
if (diff.dx.abs() > diff.dy.abs()) {
if (diff.dx > _swipeMinDist) {
direction = GameButtonDirection.left;
if (diff.dx > _dragMinDist) {
interaction = GameButtonInteraction.dragLeft;
} else {
direction = GameButtonDirection.right;
interaction = GameButtonInteraction.dragRight;
}
// vertical
} else {
if (diff.dy > _swipeMinDist) {
direction = GameButtonDirection.up;
if (diff.dy > _dragMinDist) {
interaction = GameButtonInteraction.dragUp;
} else {
direction = GameButtonDirection.down;
interaction = GameButtonInteraction.dragDown;
}
}
// pos is within button - no direction
// pos is within button - no interaction
} else {
direction = GameButtonDirection.none;
interaction = GameButtonInteraction.press;
}
return direction;
return interaction;
}
void _callAction(MUDAction? action) {
final act = action ?? data.directionalAction(GameButtonDirection.none);
final act = action ?? data.getActionOrDefault(GameButtonInteraction.press);
act.invoke(store, parentAutomation, []);
}
void _callCurrentDirection() {
_callAction(data.directionalAction(_direction));
_callAction(data.getActionOrDefault(_direction));
}
}
@@ -416,11 +431,12 @@ class GameButtonLabelData {
};
}
enum GameButtonDirection {
none,
up,
down,
left,
right,
enum GameButtonInteraction {
press,
longPress,
dragUp,
dragDown,
dragLeft,
dragRight,
}

View File

@@ -274,8 +274,8 @@ final movementPreset = GameButtonSetData(
labelUp: GameButtonLabelData(icon: Icons.exit_to_app),
labelDown: GameButtonLabelData(icon: Icons.exit_to_app),
pressAction: MUDAction('look'),
swipeUpAction: MUDAction('exits'),
swipeDownAction: MUDAction('exits'),
dragUpAction: MUDAction('exits'),
dragDownAction: MUDAction('exits'),
),
GameButtonData(
id: uuid(),

View File

@@ -29,6 +29,7 @@ class GameStore extends ChangeNotifier {
bool isCompressed = false;
final ZLibDecoder decoder = ZLibDecoder();
final msgSplitPattern = RegExp("($cr$lf)|($lf$cr)|$cr|$lf");
final outgoingMsgSplitPattern = RegExp("($cr$lf)|($lf$cr)|$cr|$lf|$csp");
final ZLibCodec _decoder = ZLibCodec();
final StreamController<List<int>> _rawStreamController = StreamController();
late Stream<List<int>> _decodedStream;
@@ -37,6 +38,10 @@ class GameStore extends ChangeNotifier {
MUDProfile? _currentProfile;
bool _clientReady = false;
// TODO move to settings
/// command separator
static const csp = ";";
// features
// TODO - move to MUDProfile and make that reactive
final List<Trigger> triggers = [];
@@ -280,21 +285,26 @@ class GameStore extends ChangeNotifier {
_client.send(line + newline);
}
void send(String line) {
if (isCompressed) {
debugPrint('sending bytes${isCompressed ? ' (compressed)' : ''}: $line');
sendBytes(line.codeUnits + newline.codeUnits);
} else {
debugPrint('sending string: $line');
sendString(line);
void send(String text) {
for (final line in text.trimRight().split(outgoingMsgSplitPattern)) {
if (isCompressed) {
debugPrint(
'sending bytes${isCompressed ? ' (compressed)' : ''}: $line');
sendBytes(line.codeUnits + newline.codeUnits);
} else {
debugPrint('sending string: $line');
sendString(line);
}
}
}
void execute(String line) {
debugPrint('processing aliases for: $line');
var sendLine = processAliases(line);
if (sendLine) {
sendString(line);
void execute(String text) {
debugPrint('processing aliases for: $text');
for (final line in text.trimRight().split(outgoingMsgSplitPattern)) {
var sendLine = processAliases(line);
if (sendLine) {
sendString(line);
}
}
}
@@ -408,3 +418,4 @@ mixin GameStoreStateMixin<T extends StatefulWidget> on State<T> {
}
final gameStore = GameStore();

View File

@@ -5,3 +5,16 @@ const _uuid = Uuid();
String uuid() {
return _uuid.v4();
}
/// split by any non-word character, or by camelCase/PascalCase
List<String> splitIntoWords(String string) {
return string
.split(RegExp(r'[\W_]+|(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])'));
}
String capitalize(String string) {
return splitIntoWords(string)
.map((word) => word[0].toUpperCase() + word.substring(1))
.join(' ');
}

View File

@@ -1,9 +1,10 @@
import 'package:flutter/material.dart';
import '../core/dialog_utils.dart';
import '../core/features/action.dart';
import '../core/features/game_button.dart';
import '../core/features/game_button_set.dart';
import '../core/platform_utils.dart';
import '../core/string_utils.dart';
class GameButtonSetPage extends StatefulWidget {
const GameButtonSetPage({super.key, required this.buttonSet});
@@ -133,7 +134,14 @@ class _ButtonSetEditorState extends State<ButtonSetEditor> {
onEdit: () {
showDialog(
context: context,
builder: (context) => ButtonEditorDialog(data: data),
builder: (context) => ButtonEditorDialog(
data: data,
onSave: (data) {
setState(() {
this.data.buttons[index] = data;
});
},
),
);
},
)
@@ -183,6 +191,7 @@ class FakeGameButton extends StatelessWidget {
Widget build(BuildContext context) {
return PopupMenuButton(
offset: Offset(0, size),
tooltip: '',
itemBuilder: (context) => const [
PopupMenuItem(
value: 'edit',
@@ -203,10 +212,10 @@ class FakeGameButton extends StatelessWidget {
label: label,
pressAction: MUDAction.empty(),
longPressAction: MUDAction.empty(),
swipeUpAction: MUDAction.empty(),
swipeDownAction: MUDAction.empty(),
swipeLeftAction: MUDAction.empty(),
swipeRightAction: MUDAction.empty(),
dragUpAction: MUDAction.empty(),
dragDownAction: MUDAction.empty(),
dragLeftAction: MUDAction.empty(),
dragRightAction: MUDAction.empty(),
),
),
);
@@ -217,9 +226,11 @@ class ButtonEditorDialog extends StatefulWidget {
const ButtonEditorDialog({
super.key,
this.data,
required this.onSave,
});
final GameButtonData? data;
final void Function(GameButtonData data) onSave;
@override
State<ButtonEditorDialog> createState() => _ButtonEditorDialogState();
@@ -236,6 +247,11 @@ class _ButtonEditorDialogState extends State<ButtonEditorDialog> {
@override
Widget build(BuildContext context) {
const interactions = GameButtonInteraction.values;
final actions = DialogUtils.saveButtons(context, () {
widget.onSave(data);
});
return Dialog(
child: SizedBox(
width: 600,
@@ -244,14 +260,37 @@ class _ButtonEditorDialogState extends State<ButtonEditorDialog> {
child: ListView(
shrinkWrap: true,
children: [
for (final direction in GameButtonDirection.values)
TextFormField(
initialValue:
data.actionForDirection(direction)?.content ?? '',
decoration: InputDecoration(
label: Text("${direction.name} action"),
),
Text(
'Edit Button',
style: Theme.of(context).textTheme.titleLarge,
),
for (final direction in interactions)
Row(
children: [
Expanded(
child: TextFormField(
initialValue: data.getAction(direction)?.content ?? '',
decoration: InputDecoration(
label: Text(capitalize(direction.name)),
),
onChanged: (value) {
data.setAction(direction, MUDAction(value));
},
),
),
const SizedBox(width: 16),
const Padding(
padding: EdgeInsets.all(8.0),
child: SizedBox(
width: 40,
height: 40,
child: Placeholder(),
),
),
],
),
const SizedBox(height: 32),
actions.row(),
],
),
),