mirror of
https://github.com/chenasraf/mudblock.git
synced 2026-05-18 01:48:57 +00:00
feat: customizable keyboard shortcuts
This commit is contained in:
@@ -23,7 +23,7 @@ class MUDAction {
|
||||
for (var i = 0; i < matches.length; i++) {
|
||||
content = content.replaceAll('%$i', matches[i]);
|
||||
}
|
||||
content = _doSpecialReplacements(store, content);
|
||||
content = doVariableReplacements(store, content);
|
||||
debugPrint('MUDAction.invoking: $content');
|
||||
|
||||
switch (target) {
|
||||
@@ -76,7 +76,7 @@ class MUDAction {
|
||||
'target': target.name,
|
||||
};
|
||||
|
||||
String _doSpecialReplacements(GameStore store, String content) {
|
||||
static String doVariableReplacements(GameStore store, String content) {
|
||||
debugPrint('MUDAction._doSpecialReplacements: $content');
|
||||
content = content
|
||||
.replaceAll('%PASSWORD', store.currentProfile.password)
|
||||
|
||||
@@ -97,6 +97,7 @@ class Automation {
|
||||
|
||||
void invokeEffect(GameStore store, String line) {
|
||||
invokeCount++;
|
||||
line = MUDAction.doVariableReplacements(store, line);
|
||||
action.invoke(store, this, allMatches(line));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:encrypt/encrypt.dart' as enc;
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../consts.dart';
|
||||
import '../keyboard_shortcuts.dart';
|
||||
import '../secrets.dart';
|
||||
import '../storage.dart';
|
||||
import '../string_utils.dart';
|
||||
@@ -149,6 +150,16 @@ class MUDProfile {
|
||||
return buttonSetFiles.map((e) => GameButtonSetData.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
Future<KeyboardShortcuts> loadKeyboardShortcuts() async {
|
||||
debugPrint('MUDProfile.loadKeyboardShortcuts: $id');
|
||||
final shortcuts =
|
||||
await ProfileStorage.readProfileFile(id, 'keyboard_shortcuts');
|
||||
if (shortcuts == null) {
|
||||
return KeyboardShortcuts.empty();
|
||||
}
|
||||
return KeyboardShortcuts.fromJson(shortcuts);
|
||||
}
|
||||
|
||||
Future<void> saveAlias(Alias alias) async {
|
||||
debugPrint('MUDProfile.saveAlias: $id/aliases/${alias.id}');
|
||||
return ProfileStorage.writeProfileFile(
|
||||
@@ -177,6 +188,12 @@ class MUDProfile {
|
||||
id, 'button_sets/${buttonSet.id}', buttonSet.toJson());
|
||||
}
|
||||
|
||||
Future<void> saveKeyboardShortcuts(KeyboardShortcuts shortcuts) async {
|
||||
debugPrint('MUDProfile.saveKeyboardShortcuts: $id');
|
||||
return ProfileStorage.writeProfileFile(
|
||||
id, 'keyboard_shortcuts', shortcuts.toJson());
|
||||
}
|
||||
|
||||
Future<void> deleteButtonSet(GameButtonSetData buttonSet) async {
|
||||
debugPrint('MUDProfile.deleteButtonSet: $id/button_sets/${buttonSet.id}');
|
||||
return ProfileStorage.deleteProfileFile(id, 'button_sets/${buttonSet.id}');
|
||||
|
||||
282
lib/core/keyboard_shortcuts.dart
Normal file
282
lib/core/keyboard_shortcuts.dart
Normal file
@@ -0,0 +1,282 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'store.dart';
|
||||
|
||||
class KeyboardIntent extends Intent {
|
||||
const KeyboardIntent(this.key, this.context);
|
||||
|
||||
final NumpadKey key;
|
||||
final BuildContext context;
|
||||
}
|
||||
|
||||
class KeyboardAction extends Action<KeyboardIntent> with GameStoreMixin {
|
||||
@override
|
||||
void invoke(covariant KeyboardIntent intent) {
|
||||
final store = storeOf(intent.context);
|
||||
store.onShortcut(intent.key, intent.context);
|
||||
}
|
||||
}
|
||||
|
||||
enum NumpadKey {
|
||||
numpad0,
|
||||
numpad1,
|
||||
numpad2,
|
||||
numpad3,
|
||||
numpad4,
|
||||
numpad5,
|
||||
numpad6,
|
||||
numpad7,
|
||||
numpad8,
|
||||
numpad9,
|
||||
numpadEnter,
|
||||
numpadDecimal,
|
||||
numpadAdd,
|
||||
numpadSubtract,
|
||||
numpadMultiply,
|
||||
numpadDivide,
|
||||
numpadEqual,
|
||||
}
|
||||
|
||||
class KeyboardShortcuts {
|
||||
String numpad0;
|
||||
String numpad1;
|
||||
String numpad2;
|
||||
String numpad3;
|
||||
String numpad4;
|
||||
String numpad5;
|
||||
String numpad6;
|
||||
String numpad7;
|
||||
String numpad8;
|
||||
String numpad9;
|
||||
String numpadEnter;
|
||||
String numpadDecimal;
|
||||
String numpadAdd;
|
||||
String numpadSubtract;
|
||||
String numpadMultiply;
|
||||
String numpadDivide;
|
||||
String numpadEqual;
|
||||
|
||||
KeyboardShortcuts({
|
||||
this.numpad0 = '',
|
||||
this.numpad1 = '',
|
||||
this.numpad2 = '',
|
||||
this.numpad3 = '',
|
||||
this.numpad4 = '',
|
||||
this.numpad5 = '',
|
||||
this.numpad6 = '',
|
||||
this.numpad7 = '',
|
||||
this.numpad8 = '',
|
||||
this.numpad9 = '',
|
||||
this.numpadEnter = '',
|
||||
this.numpadDecimal = '',
|
||||
this.numpadAdd = '',
|
||||
this.numpadSubtract = '',
|
||||
this.numpadMultiply = '',
|
||||
this.numpadDivide = '',
|
||||
this.numpadEqual = '',
|
||||
});
|
||||
|
||||
factory KeyboardShortcuts.empty() => KeyboardShortcuts(
|
||||
numpad0: '',
|
||||
numpad1: '',
|
||||
numpad2: '',
|
||||
numpad3: '',
|
||||
numpad4: '',
|
||||
numpad5: '',
|
||||
numpad6: '',
|
||||
numpad7: '',
|
||||
numpad8: '',
|
||||
numpad9: '',
|
||||
numpadEnter: '',
|
||||
numpadDecimal: '',
|
||||
numpadAdd: '',
|
||||
numpadSubtract: '',
|
||||
numpadMultiply: '',
|
||||
numpadDivide: '',
|
||||
numpadEqual: '',
|
||||
);
|
||||
|
||||
String get(NumpadKey key) =>
|
||||
{
|
||||
NumpadKey.numpad0: numpad0,
|
||||
NumpadKey.numpad1: numpad1,
|
||||
NumpadKey.numpad2: numpad2,
|
||||
NumpadKey.numpad3: numpad3,
|
||||
NumpadKey.numpad4: numpad4,
|
||||
NumpadKey.numpad5: numpad5,
|
||||
NumpadKey.numpad6: numpad6,
|
||||
NumpadKey.numpad7: numpad7,
|
||||
NumpadKey.numpad8: numpad8,
|
||||
NumpadKey.numpad9: numpad9,
|
||||
NumpadKey.numpadEnter: numpadEnter,
|
||||
NumpadKey.numpadDecimal: numpadDecimal,
|
||||
NumpadKey.numpadAdd: numpadAdd,
|
||||
NumpadKey.numpadSubtract: numpadSubtract,
|
||||
NumpadKey.numpadMultiply: numpadMultiply,
|
||||
NumpadKey.numpadDivide: numpadDivide,
|
||||
NumpadKey.numpadEqual: numpadEqual,
|
||||
}[key] ??
|
||||
'';
|
||||
|
||||
factory KeyboardShortcuts.fromJson(Map<String, dynamic> json) {
|
||||
return KeyboardShortcuts(
|
||||
numpad0: json['numpad0'] ?? '',
|
||||
numpad1: json['numpad1'] ?? '',
|
||||
numpad2: json['numpad2'] ?? '',
|
||||
numpad3: json['numpad3'] ?? '',
|
||||
numpad4: json['numpad4'] ?? '',
|
||||
numpad5: json['numpad5'] ?? '',
|
||||
numpad6: json['numpad6'] ?? '',
|
||||
numpad7: json['numpad7'] ?? '',
|
||||
numpad8: json['numpad8'] ?? '',
|
||||
numpad9: json['numpad9'] ?? '',
|
||||
numpadEnter: json['numpadEnter'] ?? '',
|
||||
numpadDecimal: json['numpadDecimal'] ?? '',
|
||||
numpadAdd: json['numpadAdd'] ?? '',
|
||||
numpadSubtract: json['numpadSubtract'] ?? '',
|
||||
numpadMultiply: json['numpadMultiply'] ?? '',
|
||||
numpadDivide: json['numpadDivide'] ?? '',
|
||||
numpadEqual: json['numpadEqual'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'numpad0': numpad0,
|
||||
'numpad1': numpad1,
|
||||
'numpad2': numpad2,
|
||||
'numpad3': numpad3,
|
||||
'numpad4': numpad4,
|
||||
'numpad5': numpad5,
|
||||
'numpad6': numpad6,
|
||||
'numpad7': numpad7,
|
||||
'numpad8': numpad8,
|
||||
'numpad9': numpad9,
|
||||
'numpadEnter': numpadEnter,
|
||||
'numpadDecimal': numpadDecimal,
|
||||
'numpadAdd': numpadAdd,
|
||||
'numpadSubtract': numpadSubtract,
|
||||
'numpadMultiply': numpadMultiply,
|
||||
'numpadDivide': numpadDivide,
|
||||
'numpadEqual': numpadEqual,
|
||||
};
|
||||
}
|
||||
|
||||
KeyboardShortcuts copyWith({
|
||||
String? numpad0,
|
||||
String? numpad1,
|
||||
String? numpad2,
|
||||
String? numpad3,
|
||||
String? numpad4,
|
||||
String? numpad5,
|
||||
String? numpad6,
|
||||
String? numpad7,
|
||||
String? numpad8,
|
||||
String? numpad9,
|
||||
String? numpadEnter,
|
||||
String? numpadDecimal,
|
||||
String? numpadAdd,
|
||||
String? numpadSubtract,
|
||||
String? numpadMultiply,
|
||||
String? numpadDivide,
|
||||
String? numpadEqual,
|
||||
}) =>
|
||||
KeyboardShortcuts(
|
||||
numpad0: numpad0 ?? this.numpad0,
|
||||
numpad1: numpad1 ?? this.numpad1,
|
||||
numpad2: numpad2 ?? this.numpad2,
|
||||
numpad3: numpad3 ?? this.numpad3,
|
||||
numpad4: numpad4 ?? this.numpad4,
|
||||
numpad5: numpad5 ?? this.numpad5,
|
||||
numpad6: numpad6 ?? this.numpad6,
|
||||
numpad7: numpad7 ?? this.numpad7,
|
||||
numpad8: numpad8 ?? this.numpad8,
|
||||
numpad9: numpad9 ?? this.numpad9,
|
||||
numpadEnter: numpadEnter ?? this.numpadEnter,
|
||||
numpadDecimal: numpadDecimal ?? this.numpadDecimal,
|
||||
numpadAdd: numpadAdd ?? this.numpadAdd,
|
||||
numpadSubtract: numpadSubtract ?? this.numpadSubtract,
|
||||
numpadMultiply: numpadMultiply ?? this.numpadMultiply,
|
||||
numpadDivide: numpadDivide ?? this.numpadDivide,
|
||||
numpadEqual: numpadEqual ?? this.numpadEqual,
|
||||
);
|
||||
|
||||
KeyboardShortcuts copyWithMap(Map<NumpadKey, String> map) => copyWith(
|
||||
numpad0: map[NumpadKey.numpad0],
|
||||
numpad1: map[NumpadKey.numpad1],
|
||||
numpad2: map[NumpadKey.numpad2],
|
||||
numpad3: map[NumpadKey.numpad3],
|
||||
numpad4: map[NumpadKey.numpad4],
|
||||
numpad5: map[NumpadKey.numpad5],
|
||||
numpad6: map[NumpadKey.numpad6],
|
||||
numpad7: map[NumpadKey.numpad7],
|
||||
numpad8: map[NumpadKey.numpad8],
|
||||
numpad9: map[NumpadKey.numpad9],
|
||||
numpadEnter: map[NumpadKey.numpadEnter],
|
||||
numpadDecimal: map[NumpadKey.numpadDecimal],
|
||||
numpadAdd: map[NumpadKey.numpadAdd],
|
||||
numpadSubtract: map[NumpadKey.numpadSubtract],
|
||||
numpadMultiply: map[NumpadKey.numpadMultiply],
|
||||
numpadDivide: map[NumpadKey.numpadDivide],
|
||||
numpadEqual: map[NumpadKey.numpadEqual],
|
||||
);
|
||||
}
|
||||
|
||||
Map<ShortcutActivator, Intent> numpadKeysIntentMap(BuildContext context) =>
|
||||
<ShortcutActivator, Intent>{
|
||||
// SingleActivator(LogicalKeyboardKey.enter): ActivateIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.numpad0):
|
||||
KeyboardIntent(NumpadKey.numpad0, context),
|
||||
const SingleActivator(LogicalKeyboardKey.numpad1):
|
||||
KeyboardIntent(NumpadKey.numpad1, context),
|
||||
const SingleActivator(LogicalKeyboardKey.numpad2):
|
||||
KeyboardIntent(NumpadKey.numpad2, context),
|
||||
const SingleActivator(LogicalKeyboardKey.numpad3):
|
||||
KeyboardIntent(NumpadKey.numpad3, context),
|
||||
const SingleActivator(LogicalKeyboardKey.numpad4):
|
||||
KeyboardIntent(NumpadKey.numpad4, context),
|
||||
const SingleActivator(LogicalKeyboardKey.numpad5):
|
||||
KeyboardIntent(NumpadKey.numpad5, context),
|
||||
const SingleActivator(LogicalKeyboardKey.numpad6):
|
||||
KeyboardIntent(NumpadKey.numpad6, context),
|
||||
const SingleActivator(LogicalKeyboardKey.numpad7):
|
||||
KeyboardIntent(NumpadKey.numpad7, context),
|
||||
const SingleActivator(LogicalKeyboardKey.numpad8):
|
||||
KeyboardIntent(NumpadKey.numpad8, context),
|
||||
const SingleActivator(LogicalKeyboardKey.numpad9):
|
||||
KeyboardIntent(NumpadKey.numpad9, context),
|
||||
const SingleActivator(LogicalKeyboardKey.numpadDivide):
|
||||
KeyboardIntent(NumpadKey.numpadDivide, context),
|
||||
const SingleActivator(LogicalKeyboardKey.numpadMultiply):
|
||||
KeyboardIntent(NumpadKey.numpadMultiply, context),
|
||||
const SingleActivator(LogicalKeyboardKey.numpadSubtract):
|
||||
KeyboardIntent(NumpadKey.numpadSubtract, context),
|
||||
const SingleActivator(LogicalKeyboardKey.numpadAdd):
|
||||
KeyboardIntent(NumpadKey.numpadAdd, context),
|
||||
const SingleActivator(LogicalKeyboardKey.numpadDecimal):
|
||||
KeyboardIntent(NumpadKey.numpadDecimal, context),
|
||||
const SingleActivator(LogicalKeyboardKey.numpadEnter):
|
||||
KeyboardIntent(NumpadKey.numpadEnter, context),
|
||||
};
|
||||
|
||||
final numpadKeyLabels = {
|
||||
NumpadKey.numpad0: '0',
|
||||
NumpadKey.numpad1: '1',
|
||||
NumpadKey.numpad2: '2',
|
||||
NumpadKey.numpad3: '3',
|
||||
NumpadKey.numpad4: '4',
|
||||
NumpadKey.numpad5: '5',
|
||||
NumpadKey.numpad6: '6',
|
||||
NumpadKey.numpad7: '7',
|
||||
NumpadKey.numpad8: '8',
|
||||
NumpadKey.numpad9: '9',
|
||||
NumpadKey.numpadEnter: 'Enter',
|
||||
NumpadKey.numpadDecimal: '.',
|
||||
NumpadKey.numpadAdd: '+',
|
||||
NumpadKey.numpadSubtract: '-',
|
||||
NumpadKey.numpadMultiply: '*',
|
||||
NumpadKey.numpadDivide: '/',
|
||||
NumpadKey.numpadEqual: '=',
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mudblock/pages/keyboard_shortcuts_page.dart';
|
||||
|
||||
import '../core/features/alias.dart';
|
||||
import '../core/features/trigger.dart';
|
||||
@@ -15,7 +16,6 @@ import '../pages/trigger_list_page.dart';
|
||||
import '../pages/trigger_page.dart';
|
||||
import '../pages/variable_list_page.dart';
|
||||
import '../pages/variable_page.dart';
|
||||
import 'consts.dart';
|
||||
import 'features/game_button_set.dart';
|
||||
import 'features/profile.dart';
|
||||
import 'features/variable.dart';
|
||||
@@ -39,6 +39,8 @@ class Paths {
|
||||
static const buttonSet = '/button-set';
|
||||
static const button = '/button';
|
||||
|
||||
static const shortcuts = '/shortcuts';
|
||||
|
||||
static const settings = '/settings';
|
||||
}
|
||||
|
||||
@@ -85,9 +87,21 @@ final routes = <String, Widget Function(BuildContext)>{
|
||||
ModalRoute.of(context)!.settings.arguments as GameButtonSetData?;
|
||||
return GameButtonSetPage(buttonSet: buttonSet);
|
||||
},
|
||||
Paths.shortcuts: (context) {
|
||||
return GameStore.consumer(builder: (context, store, child) {
|
||||
return KeyboardShortcutsPage(
|
||||
shortcuts: store.keyboardShortcuts,
|
||||
onSave: (shortcuts) {
|
||||
store.keyboardShortcuts = shortcuts;
|
||||
store.currentProfile.saveKeyboardShortcuts(shortcuts);
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
Paths.home: (context) => HomeScaffold(
|
||||
builder: (context, _) {
|
||||
return const HomePage();
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
|
||||
@@ -60,15 +60,18 @@ class FileStorage {
|
||||
}
|
||||
|
||||
class ProfileStorage {
|
||||
static const encoder = JsonEncoder.withIndent(' ');
|
||||
static const decoder = JsonDecoder();
|
||||
|
||||
static Future<Map<String, dynamic>?> readProfileFile(
|
||||
String profile, String filename) async {
|
||||
final data = await FileStorage.readFile('profiles/$profile/$filename.json');
|
||||
return data != null ? jsonDecode(data) : null;
|
||||
return data != null ? decoder.convert(data) : null;
|
||||
}
|
||||
|
||||
static Future<void> writeProfileFile(
|
||||
String profile, String filename, dynamic data) async {
|
||||
data = jsonEncode(data);
|
||||
data = encoder.convert(data);
|
||||
await FileStorage.writeFile('profiles/$profile/$filename.json', data);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import 'features/game_button_set.dart';
|
||||
import 'features/profile.dart';
|
||||
import 'features/trigger.dart';
|
||||
import 'features/variable.dart';
|
||||
import 'keyboard_shortcuts.dart';
|
||||
|
||||
const maxLines = 2000;
|
||||
|
||||
@@ -48,6 +49,7 @@ class GameStore extends ChangeNotifier {
|
||||
final List<Alias> aliases = [];
|
||||
final Map<String, Variable> variables = {};
|
||||
final List<GameButtonSetData> buttonSets = [];
|
||||
KeyboardShortcuts keyboardShortcuts = KeyboardShortcuts();
|
||||
|
||||
MUDProfile get currentProfile => _currentProfile!;
|
||||
|
||||
@@ -83,6 +85,7 @@ class GameStore extends ChangeNotifier {
|
||||
loadAliases(),
|
||||
loadVariables(),
|
||||
loadButtonSets(),
|
||||
loadKeyboardShortcuts(),
|
||||
]);
|
||||
_client.connect();
|
||||
}
|
||||
@@ -120,6 +123,13 @@ class GameStore extends ChangeNotifier {
|
||||
debugPrint('ButtonSets: ${buttonSets.length}');
|
||||
}
|
||||
|
||||
Future<void> loadKeyboardShortcuts() async {
|
||||
final shortcuts = await currentProfile.loadKeyboardShortcuts();
|
||||
keyboardShortcuts = shortcuts;
|
||||
notifyListeners();
|
||||
debugPrint('KeyboardShortcuts loaded');
|
||||
}
|
||||
|
||||
bool processTriggers(String line) {
|
||||
bool showLine = true;
|
||||
final str = ColorUtils.stripColor(line);
|
||||
@@ -422,6 +432,13 @@ class GameStore extends ChangeNotifier {
|
||||
debugPrint('profiles: ${profiles.map((e) => [e.name, e.password])}');
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void onShortcut(NumpadKey key, BuildContext context) {
|
||||
final action = keyboardShortcuts.get(key);
|
||||
if (action.isNotEmpty) {
|
||||
submitInput(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mixin GameStoreMixin {
|
||||
@@ -434,3 +451,4 @@ mixin GameStoreStateMixin<T extends StatefulWidget> on State<T> {
|
||||
}
|
||||
|
||||
final gameStore = GameStore();
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../core/features/game_button_set.dart';
|
||||
import '../core/platform_utils.dart';
|
||||
import '../core/string_utils.dart';
|
||||
import '../widgets/button_set_editor.dart';
|
||||
|
||||
class GameButtonSetPage extends StatefulWidget {
|
||||
@@ -24,6 +26,7 @@ class _GameButtonSetPageState extends State<GameButtonSetPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final platformWindowName = PlatformUtils.isDesktop ? 'window' : 'screen';
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Button Set'),
|
||||
@@ -74,7 +77,7 @@ class _GameButtonSetPageState extends State<GameButtonSetPage> {
|
||||
.map(
|
||||
(e) => DropdownMenuEntry(
|
||||
value: e,
|
||||
label: e.name,
|
||||
label: capitalize(e.name),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
@@ -85,12 +88,40 @@ class _GameButtonSetPageState extends State<GameButtonSetPage> {
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
DropdownMenu(
|
||||
label: Text('Position on $platformWindowName'),
|
||||
initialSelection: buttonSet.alignment,
|
||||
dropdownMenuEntries: [
|
||||
Alignment.topLeft,
|
||||
Alignment.topCenter,
|
||||
Alignment.topRight,
|
||||
Alignment.centerLeft,
|
||||
Alignment.center,
|
||||
Alignment.centerRight,
|
||||
Alignment.bottomLeft,
|
||||
Alignment.bottomCenter,
|
||||
Alignment.bottomRight,
|
||||
]
|
||||
.map(
|
||||
(e) => DropdownMenuEntry(
|
||||
value: e,
|
||||
label: capitalize(e.toString().split('.')[1]),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onSelected: (value) {
|
||||
setState(() {
|
||||
buttonSet.alignment = value as Alignment;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ButtonSetEditor(
|
||||
key: Key(buttonSet.type.name),
|
||||
data: buttonSet,
|
||||
onUpdate: (data) {
|
||||
setState(() {
|
||||
buttonSet = data;
|
||||
buttonSet = buttonSet.copyWith(buttons: data.buttons);
|
||||
});
|
||||
},
|
||||
),
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import '../core/consts.dart';
|
||||
import '../core/features/game_button_set.dart';
|
||||
import '../core/keyboard_shortcuts.dart';
|
||||
import '../core/store.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
@@ -43,99 +44,109 @@ class HomePageState extends State<HomePage>
|
||||
);
|
||||
final inputStyle = consoleStyle.copyWith(color: Colors.grey);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Material(
|
||||
color: Colors.black,
|
||||
child: Consumer<GameStore>(
|
||||
builder: (context, store, child) {
|
||||
final lines = store.lines;
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
cursorColor: Colors.white,
|
||||
selectionColor: Colors.white.withOpacity(0.3),
|
||||
selectionHandleColor: Colors.white,
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: store.selectInput,
|
||||
child: SingleChildScrollView(
|
||||
controller: store.scrollController,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SelectableText.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
for (final line in lines) ...[
|
||||
for (final segment
|
||||
in ColorUtils.split(line))
|
||||
TextSpan(
|
||||
text: segment.text,
|
||||
style: consoleStyle.copyWith(
|
||||
color: Color(segment.themedFgColor),
|
||||
backgroundColor:
|
||||
Color(segment.themedBgColor),
|
||||
fontWeight: segment.bold
|
||||
? FontWeight.w800
|
||||
: null,
|
||||
fontStyle: segment.italic
|
||||
? FontStyle.italic
|
||||
: null,
|
||||
decoration: segment.underline
|
||||
? TextDecoration.underline
|
||||
: null,
|
||||
),
|
||||
),
|
||||
const TextSpan(
|
||||
text: newline,
|
||||
style:
|
||||
consoleStyle, // .copyWith(fontSize: 1),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
enableInteractiveSelection: true,
|
||||
selectionWidthStyle: BoxWidthStyle.tight,
|
||||
selectionHeightStyle: BoxHeightStyle.max,
|
||||
),
|
||||
return Shortcuts(
|
||||
shortcuts: numpadKeysIntentMap(context),
|
||||
child: Actions(
|
||||
actions: {
|
||||
KeyboardIntent: KeyboardAction(),
|
||||
},
|
||||
child: Focus(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Material(
|
||||
color: Colors.black,
|
||||
child: Consumer<GameStore>(
|
||||
builder: (context, store, child) {
|
||||
final lines = store.lines;
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
cursorColor: Colors.white,
|
||||
selectionColor: Colors.white.withOpacity(0.3),
|
||||
selectionHandleColor: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
for (final buttonSet
|
||||
in store.buttonSets.where((b) => b.enabled))
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: GameButtonSet(data: buttonSet),
|
||||
)
|
||||
],
|
||||
child: Stack(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: store.selectInput,
|
||||
child: SingleChildScrollView(
|
||||
controller: store.scrollController,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SelectableText.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
for (final line in lines) ...[
|
||||
for (final segment
|
||||
in ColorUtils.split(line))
|
||||
TextSpan(
|
||||
text: segment.text,
|
||||
style: consoleStyle.copyWith(
|
||||
color: Color(segment.themedFgColor),
|
||||
backgroundColor:
|
||||
Color(segment.themedBgColor),
|
||||
fontWeight: segment.bold
|
||||
? FontWeight.w800
|
||||
: null,
|
||||
fontStyle: segment.italic
|
||||
? FontStyle.italic
|
||||
: null,
|
||||
decoration: segment.underline
|
||||
? TextDecoration.underline
|
||||
: null,
|
||||
),
|
||||
),
|
||||
const TextSpan(
|
||||
text: newline,
|
||||
style:
|
||||
consoleStyle, // .copyWith(fontSize: 1),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
enableInteractiveSelection: true,
|
||||
selectionWidthStyle: BoxWidthStyle.tight,
|
||||
selectionHeightStyle: BoxHeightStyle.max,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
for (final buttonSet
|
||||
in store.buttonSets.where((b) => b.enabled))
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: GameButtonSet(data: buttonSet),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextField(
|
||||
autofocus: true,
|
||||
focusNode: store.inputFocus,
|
||||
controller: store.input,
|
||||
onSubmitted: store.submitInput,
|
||||
onTap: store.selectInput,
|
||||
style: consoleStyle.copyWith(color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Enter command',
|
||||
border: const OutlineInputBorder(),
|
||||
hintStyle: inputStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextField(
|
||||
autofocus: true,
|
||||
focusNode: store.inputFocus,
|
||||
controller: store.input,
|
||||
onSubmitted: store.submitInput,
|
||||
onTap: store.selectInput,
|
||||
style: consoleStyle.copyWith(color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Enter command',
|
||||
border: const OutlineInputBorder(),
|
||||
hintStyle: inputStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -92,6 +92,11 @@ class HomeScaffold extends StatelessWidget with GameStoreMixin {
|
||||
leading: const Icon(Variable.iconData),
|
||||
onTap: () => Navigator.pushNamed(context, Paths.variables),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Keyboard Shortcuts'),
|
||||
leading: const Icon(Icons.keyboard),
|
||||
onTap: () => Navigator.pushNamed(context, Paths.shortcuts),
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
title: const Text('Settings'),
|
||||
@@ -115,3 +120,4 @@ class HomeScaffold extends StatelessWidget with GameStoreMixin {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
135
lib/pages/keyboard_shortcuts_page.dart
Normal file
135
lib/pages/keyboard_shortcuts_page.dart
Normal file
@@ -0,0 +1,135 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../core/keyboard_shortcuts.dart';
|
||||
|
||||
class KeyboardShortcutsPage extends StatefulWidget {
|
||||
const KeyboardShortcutsPage({super.key, required this.shortcuts, required this.onSave});
|
||||
|
||||
final KeyboardShortcuts shortcuts;
|
||||
final void Function(KeyboardShortcuts) onSave;
|
||||
|
||||
@override
|
||||
State<KeyboardShortcutsPage> createState() => _KeyboardShortcutsPageState();
|
||||
}
|
||||
|
||||
class _KeyboardShortcutsPageState extends State<KeyboardShortcutsPage> {
|
||||
late KeyboardShortcuts shortucts;
|
||||
|
||||
final _controllers = {
|
||||
NumpadKey.numpad0: TextEditingController(),
|
||||
NumpadKey.numpad1: TextEditingController(),
|
||||
NumpadKey.numpad2: TextEditingController(),
|
||||
NumpadKey.numpad3: TextEditingController(),
|
||||
NumpadKey.numpad4: TextEditingController(),
|
||||
NumpadKey.numpad5: TextEditingController(),
|
||||
NumpadKey.numpad6: TextEditingController(),
|
||||
NumpadKey.numpad7: TextEditingController(),
|
||||
NumpadKey.numpad8: TextEditingController(),
|
||||
NumpadKey.numpad9: TextEditingController(),
|
||||
NumpadKey.numpadEnter: TextEditingController(),
|
||||
NumpadKey.numpadDecimal: TextEditingController(),
|
||||
NumpadKey.numpadAdd: TextEditingController(),
|
||||
NumpadKey.numpadSubtract: TextEditingController(),
|
||||
NumpadKey.numpadMultiply: TextEditingController(),
|
||||
NumpadKey.numpadDivide: TextEditingController(),
|
||||
NumpadKey.numpadEqual: TextEditingController(),
|
||||
};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
shortucts = widget.shortcuts.copyWith();
|
||||
_controllers.forEach((key, controller) {
|
||||
controller.text = shortucts.get(key);
|
||||
controller.addListener(() {
|
||||
setState(() {
|
||||
shortucts = shortucts.copyWithMap({key: controller.text});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ordered = <List<NumpadKey?>>[
|
||||
[
|
||||
null,
|
||||
null,
|
||||
NumpadKey.numpadDivide,
|
||||
NumpadKey.numpadMultiply,
|
||||
],
|
||||
[
|
||||
NumpadKey.numpad7,
|
||||
NumpadKey.numpad8,
|
||||
NumpadKey.numpad9,
|
||||
NumpadKey.numpadSubtract
|
||||
],
|
||||
[
|
||||
NumpadKey.numpad4,
|
||||
NumpadKey.numpad5,
|
||||
NumpadKey.numpad6,
|
||||
NumpadKey.numpadAdd
|
||||
],
|
||||
[
|
||||
NumpadKey.numpad1,
|
||||
NumpadKey.numpad2,
|
||||
NumpadKey.numpad3,
|
||||
NumpadKey.numpadEqual
|
||||
],
|
||||
[
|
||||
NumpadKey.numpad0,
|
||||
null,
|
||||
NumpadKey.numpadDecimal,
|
||||
null,
|
||||
],
|
||||
];
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Keyboard Shortcuts'),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () {
|
||||
widget.onSave(shortucts);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Icon(Icons.save),
|
||||
),
|
||||
body: Center(
|
||||
child: SizedBox(
|
||||
width: 800,
|
||||
child: Column(
|
||||
children: ordered
|
||||
.map(
|
||||
(row) => Row(
|
||||
children: row
|
||||
.map(
|
||||
(key) => Expanded(
|
||||
child: key != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: TextField(
|
||||
controller: _controllers[key],
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText:
|
||||
numpadKeyLabels[key] ?? key.name,
|
||||
floatingLabelBehavior:
|
||||
FloatingLabelBehavior.always,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user