diff --git a/app/lib/builder/provider_generator.dart b/app/lib/builder/provider_generator.dart index 205c260..513fb12 100644 --- a/app/lib/builder/provider_generator.dart +++ b/app/lib/builder/provider_generator.dart @@ -22,7 +22,9 @@ class ProviderGenerator extends GeneratorForAnnotation { List fields = []; element.fields.forEach((field) { var annotation = field.metadata.firstWhereOrNull( - (m) => m.computeConstantValue()?.type?.getDisplayString(withNullability: true) == 'ProviderModelProp'); + (m) => m.computeConstantValue()?.type?.getDisplayString(withNullability: true) == 'ProviderModelProp') ?? + field.getter?.metadata.firstWhereOrNull( + (m) => m.computeConstantValue()?.type?.getDisplayString(withNullability: true) == 'ProviderModelProp'); if (annotation != null) fields.add( AnnotationField( @@ -77,7 +79,7 @@ String? _genCopyFunc(String displayName, List fields, bool copy return ''' void copyFrom(${displayName} other) { bool changed = false; - ${fields.map((f) => 'if (other.${f.name}.diff(this.${f.name})) {this.${f.name} = other.${f.name}; changed = true; }').join('\n')} + ${fields.map((f) => 'if (other.${f.name} != this.${f.name}) {this.${f.name} = other.${f.name}; changed = true; }').join('\n')} if (changed) notifyListeners(); } '''; diff --git a/app/lib/constants/constants.dart b/app/lib/constants/constants.dart index 848ac13..b57bd45 100644 --- a/app/lib/constants/constants.dart +++ b/app/lib/constants/constants.dart @@ -20,6 +20,23 @@ const double defaultBorderRadius = 8; const double defaultButtonHeight = 36; +const List builtInCommands = [ + 'ShowWorkspace', + 'Handle4Or5FingersSwipeUp', + 'Handle4Or5FingersSwipeDown', + 'ToggleMaximize', + 'Minimize', + 'ShowWindow', + 'ShowAllWindow', + 'SwitchApplication', + 'ReverseSwitchApplication', + 'SwitchWorkspace', + 'ReverseSwitchWorkspace', + 'SplitWindowLeft', + 'SplitWindowRight', + 'MoveWindow', +]; + enum PanelType { local_manager, market, diff --git a/app/lib/constants/supported_locales.dart b/app/lib/constants/supported_locales.dart index 0897879..0524221 100644 --- a/app/lib/constants/supported_locales.dart +++ b/app/lib/constants/supported_locales.dart @@ -18,5 +18,7 @@ const supportedLocaleNames = { SupportedLocale.en: 'English', }; -Locale getSupportedLocale(SupportedLocale supportedLocale) => supportedLocales[supportedLocale.index]; +Locale transformSupportedLocale(SupportedLocale supportedLocale) => supportedLocales[supportedLocale.index]; +SupportedLocale? getSupportedLocale(Locale? locale) => + supportedLocales.contains(locale) ? SupportedLocale.values[supportedLocales.indexOf(locale!)] : null; diff --git a/app/lib/models/local_schemes.dart b/app/lib/models/local_schemes.dart new file mode 100644 index 0000000..8d841df --- /dev/null +++ b/app/lib/models/local_schemes.dart @@ -0,0 +1,28 @@ +import 'package:dde_gesture_manager/models/scheme.dart'; + +export 'local_schemes_web.dart' if (dart.library.io) 'local_schemes_linux.dart'; + +abstract class LocalSchemeEntry { + Scheme scheme; + DateTime lastModifyTime; + String path; + + LocalSchemeEntry({ + required this.path, + required this.scheme, + required this.lastModifyTime, + }); + + LocalSchemeEntry.systemDefault() + : this.path = '', + this.scheme = Scheme.systemDefault(), + + /// max value of DateTime ![Time Values and Time Range](https://262.ecma-international.org/11.0/#sec-time-values-and-time-range) + this.lastModifyTime = DateTime.fromMillisecondsSinceEpoch(8640000000000000); + + save(); +} + +abstract class LocalSchemesInterface { + Future> get schemeEntries; +} \ No newline at end of file diff --git a/app/lib/models/local_schemes_linux.dart b/app/lib/models/local_schemes_linux.dart new file mode 100644 index 0000000..105dd79 --- /dev/null +++ b/app/lib/models/local_schemes_linux.dart @@ -0,0 +1,74 @@ +import 'dart:io'; + +import 'package:dde_gesture_manager/builder/provider_annotation.dart'; +import 'package:dde_gesture_manager/extensions.dart'; +import 'package:dde_gesture_manager/models/scheme.dart'; +import 'package:path/path.dart' show join; +import 'package:path_provider/path_provider.dart'; + +import 'local_schemes.dart'; + +export 'local_schemes.dart'; + +@ProviderModel() +class LocalSchemes implements LocalSchemesInterface { + LocalSchemes() { + schemeEntries.then((value) => schemes = [LocalSchemeEntryLinux.systemDefault(), ...value]); + } + + @override + Future> get schemeEntries async { + var _supportDirectory = await getApplicationSupportDirectory(); + var directory = Directory(join(_supportDirectory.path, 'schemes')); + if (!directory.existsSync()) directory.createSync(); + return directory + .list() + .map((f) { + LocalSchemeEntryLinux? entry; + try { + var content = File(f.path).readAsStringSync(); + entry = LocalSchemeEntryLinux( + path: f.path, scheme: Scheme.parse(content), lastModifyTime: f.statSync().modified); + } catch (e) { + e.sout(); + } + return entry; + }) + .where((e) => e != null) + .cast() + .toList(); + } + + @ProviderModelProp() + List? schemes; +} + +class LocalSchemeEntryLinux implements LocalSchemeEntry { + @override + String path; + + @override + Scheme scheme; + + @override + DateTime lastModifyTime; + + LocalSchemeEntryLinux({ + required this.path, + required this.scheme, + required this.lastModifyTime, + }); + + LocalSchemeEntryLinux.systemDefault() + : this.path = '', + this.scheme = Scheme.systemDefault(), + + /// max value of DateTime ![Time Values and Time Range](https://262.ecma-international.org/11.0/#sec-time-values-and-time-range) + this.lastModifyTime = DateTime.fromMillisecondsSinceEpoch(8640000000000000); + + @override + save() { + // TODO: implement save + throw UnimplementedError(); + } +} diff --git a/app/lib/models/local_schemes_provider.dart b/app/lib/models/local_schemes_provider.dart new file mode 100644 index 0000000..3e23c89 --- /dev/null +++ b/app/lib/models/local_schemes_provider.dart @@ -0,0 +1 @@ +export 'local_schemes_web.provider.dart' if (dart.library.io) 'local_schemes_linux.provider.dart'; \ No newline at end of file diff --git a/app/lib/models/local_schemes_web.dart b/app/lib/models/local_schemes_web.dart new file mode 100644 index 0000000..c8c1814 --- /dev/null +++ b/app/lib/models/local_schemes_web.dart @@ -0,0 +1,75 @@ +import 'dart:convert'; +import 'dart:html'; + +import 'package:dde_gesture_manager/builder/provider_annotation.dart'; +import 'package:dde_gesture_manager/extensions.dart'; +import 'package:dde_gesture_manager/models/scheme.dart'; + +import 'local_schemes.dart'; + +export 'local_schemes.dart'; + +@ProviderModel() +class LocalSchemes implements LocalSchemesInterface { + LocalSchemes() { + schemeEntries.then((value) => schemes = [LocalSchemeEntryWeb.systemDefault(), ...value]); + } + + @override + Future> get schemeEntries async { + return window.localStorage.keys + .map((key) { + if (key.startsWith('schemes.')) { + LocalSchemeEntryWeb? entry; + try { + var content = window.localStorage[key] ?? ''; + var schemeJson = json.decode(content); + entry = LocalSchemeEntryWeb( + path: key, + scheme: Scheme.parse(schemeJson), + lastModifyTime: DateTime.parse(schemeJson['modified_at']), + ); + } catch (e) { + e.sout(); + } + return entry; + } + }) + .where((e) => e != null) + .cast() + .toList(); + } + + @ProviderModelProp() + List? schemes; +} + +class LocalSchemeEntryWeb implements LocalSchemeEntry { + @override + String path; + + @override + Scheme scheme; + + @override + DateTime lastModifyTime; + + LocalSchemeEntryWeb({ + required this.path, + required this.scheme, + required this.lastModifyTime, + }); + + LocalSchemeEntryWeb.systemDefault() + : this.path = '', + this.scheme = Scheme.systemDefault(), + + /// max value of DateTime ![Time Values and Time Range](https://262.ecma-international.org/11.0/#sec-time-values-and-time-range) + this.lastModifyTime = DateTime.fromMillisecondsSinceEpoch(8640000000000000); + + @override + save() { + // TODO: implement save + throw UnimplementedError(); + } +} diff --git a/app/lib/models/local_solutions.dart b/app/lib/models/local_solutions.dart deleted file mode 100644 index e2249ed..0000000 --- a/app/lib/models/local_solutions.dart +++ /dev/null @@ -1,21 +0,0 @@ -export 'local_solutions_web.dart' if (dart.library.io) 'local_solutions_linux.dart'; - -import 'package:dde_gesture_manager/models/solution.dart'; - -abstract class LocalSolutionEntry { - Solution solution; - DateTime lastModifyTime; - String path; - - LocalSolutionEntry({ - required this.path, - required this.solution, - required this.lastModifyTime, - }); - - save(); -} - -abstract class LocalSolutionsInterface { - Future> get solutionEntries; -} \ No newline at end of file diff --git a/app/lib/models/local_solutions_linux.dart b/app/lib/models/local_solutions_linux.dart deleted file mode 100644 index 80cc63e..0000000 --- a/app/lib/models/local_solutions_linux.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'dart:io'; - -import 'package:dde_gesture_manager/builder/provider_annotation.dart'; -import 'package:dde_gesture_manager/extensions.dart'; -import 'package:dde_gesture_manager/models/solution.dart'; -import 'package:path/path.dart' show join; -import 'package:path_provider/path_provider.dart'; - -import 'local_solutions.dart'; - -export 'local_solutions.dart'; - -@ProviderModel() -class LocalSolutions implements LocalSolutionsInterface { - LocalSolutions() { - solutionEntries.then((value) => solutions = value); - } - - @override - Future> get solutionEntries async { - var _supportDirectory = await getApplicationSupportDirectory(); - var directory = Directory(join(_supportDirectory.path, 'solutions')); - if (!directory.existsSync()) directory.createSync(); - directory.path.sout(); - return directory - .list() - .map((f) { - LocalSolutionEntryLinux? entry; - try { - var content = File(f.path).readAsStringSync(); - entry = LocalSolutionEntryLinux( - path: f.path, solution: Solution.parse(content), lastModifyTime: f.statSync().modified); - } catch (e) { - e.sout(); - } - return entry; - }) - .where((e) => e != null) - .cast() - .toList(); - } - - @ProviderModelProp() - List? solutions; -} - -class LocalSolutionEntryLinux implements LocalSolutionEntry { - @override - String path; - - @override - Solution solution; - - @override - DateTime lastModifyTime; - - LocalSolutionEntryLinux({ - required this.path, - required this.solution, - required this.lastModifyTime, - }); - - @override - save() { - // TODO: implement save - throw UnimplementedError(); - } -} diff --git a/app/lib/models/local_solutions_provider.dart b/app/lib/models/local_solutions_provider.dart deleted file mode 100644 index 6a10556..0000000 --- a/app/lib/models/local_solutions_provider.dart +++ /dev/null @@ -1 +0,0 @@ -export 'local_solutions_web.provider.dart' if (dart.library.io) 'local_solutions_linux.provider.dart'; \ No newline at end of file diff --git a/app/lib/models/local_solutions_web.dart b/app/lib/models/local_solutions_web.dart deleted file mode 100644 index 81faed7..0000000 --- a/app/lib/models/local_solutions_web.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'dart:convert'; - -import 'package:dde_gesture_manager/builder/provider_annotation.dart'; -import 'package:dde_gesture_manager/models/solution.dart'; -import 'package:dde_gesture_manager/extensions.dart'; -import 'dart:html'; - -import 'local_solutions.dart'; -export 'local_solutions.dart'; - -@ProviderModel() -class LocalSolutions implements LocalSolutionsInterface { - LocalSolutions() { - solutionEntries.then((value) => solutions = value); - } - - @override - Future> get solutionEntries async { - return window.localStorage.keys - .map((key) { - if (key.startsWith('solutions.')) { - LocalSolutionEntryWeb? entry; - try { - var content = window.localStorage[key] ?? ''; - var solutionJson = json.decode(content); - entry = LocalSolutionEntryWeb( - path: key, - solution: Solution.parse(solutionJson), - lastModifyTime: DateTime.parse(solutionJson['modified_at']), - ); - } catch (e) { - e.sout(); - } - return entry; - } - }) - .where((e) => e != null) - .cast() - .toList(); - } - - @ProviderModelProp() - List? solutions; -} - -class LocalSolutionEntryWeb implements LocalSolutionEntry { - @override - String path; - - @override - Solution solution; - - @override - DateTime lastModifyTime; - - LocalSolutionEntryWeb({ - required this.path, - required this.solution, - required this.lastModifyTime, - }); - - @override - save() { - // TODO: implement save - throw UnimplementedError(); - } -} diff --git a/app/lib/models/scheme.dart b/app/lib/models/scheme.dart new file mode 100644 index 0000000..4b54d4f --- /dev/null +++ b/app/lib/models/scheme.dart @@ -0,0 +1,144 @@ +import 'dart:convert'; + +import 'package:dde_gesture_manager/builder/provider_annotation.dart'; +import 'package:dde_gesture_manager/extensions.dart'; +import 'package:dde_gesture_manager/extensions/compare_extension.dart'; +import 'package:dde_gesture_manager/utils/helper.dart'; +import 'package:uuid/uuid.dart'; + +typedef OnEditEnd(GestureProp prop); + +@ProviderModel(copyable: true) +class Scheme { + @ProviderModelProp() + String? id; + + @ProviderModelProp() + String? name; + + @ProviderModelProp() + String? description; + + @ProviderModelProp() + List? gestures; + + Scheme.parse(scheme) { + if (scheme is String) scheme = json.decode(scheme); + assert(scheme is Map); + id = scheme['id']; + name = scheme['name']; + description = scheme['desc']; + gestures = (scheme['gestures'] as List? ?? []).map((ele) => GestureProp.parse(ele)).toList()..sort(); + } + + Scheme.systemDefault() { + this.id = Uuid.NAMESPACE_NIL; + this.name = LocaleKeys.local_manager_default_scheme_label.tr(); + this.description = LocaleKeys.local_manager_default_scheme_description.tr(); + this.gestures = []; + } + + Scheme.create({this.name, this.description, this.gestures}) { + this.id = Uuid().v1(); + } +} + +enum Gesture { + tap, + swipe, + pinch, +} + +enum GestureDirection { + up, + down, + left, + right, + pinch_in, + pinch_out, + none, +} + +enum GestureType { + built_in, + commandline, + shortcut, +} + +@ProviderModel(copyable: true) +class GestureProp implements Comparable { + @ProviderModelProp() + String? id; + + @ProviderModelProp() + Gesture? gesture; + + @ProviderModelProp() + GestureDirection? direction; + + @ProviderModelProp() + int? fingers; + + @ProviderModelProp() + GestureType? type; + + @ProviderModelProp() + String? command; + + @ProviderModelProp() + String? remark; + + @ProviderModelProp() + bool? get editMode => _editMode; + + set editMode(bool? val) { + _editMode = val ?? false; + if (val == false) onEditEnd?.call(this); + } + + OnEditEnd? onEditEnd; + + bool _editMode = false; + + @override + bool operator ==(Object other) => other is GestureProp && other.id == this.id; + + @override + String toString() { + return 'GestureProp{gesture: $gesture, direction: $direction, fingers: $fingers, type: $type, command: $command, remark: $remark}'; + } + + GestureProp.empty() : this.id = Uuid.NAMESPACE_NIL; + + GestureProp.parse(props) { + if (props is String) props = json.decode(props); + assert(props is Map); + id = Uuid().v1(); + gesture = H.getGestureByName(props['gesture']); + direction = H.getGestureDirectionByName(props['direction']); + fingers = props['fingers']; + type = H.getGestureTypeByName(props['type']); + command = props['command']; + remark = props['remark']; + } + + copyFrom(GestureProp prop) { + this.id = prop.id; + this.gesture = prop.gesture; + this.direction = prop.direction; + this.fingers = prop.fingers; + this.type = prop.type; + this.command = prop.command; + this.remark = prop.remark; + } + + @override + int compareTo(other) { + assert(other is GestureProp); + if (fingers.diff(other.fingers) && other.fingers != null) return fingers! - other.fingers as int; + if (gesture.diff(other.gesture) && other.gesture != null) return gesture!.index - other.gesture!.index as int; + if (direction.diff(other.direction) && other.direction != null) + return direction!.index - other.direction!.index as int; + return 0; + } +} diff --git a/app/lib/models/settings.dart b/app/lib/models/settings.dart index 27a264d..844b0a4 100644 --- a/app/lib/models/settings.dart +++ b/app/lib/models/settings.dart @@ -1,7 +1,15 @@ import 'package:dde_gesture_manager/builder/provider_annotation.dart'; +import 'package:flutter/material.dart'; + +export 'package:flutter/material.dart' show Color; @ProviderModel() class Settings { @ProviderModelProp() bool? isDarkMode; + + @ProviderModelProp() + Color? activeColor; + + Color get currentActiveColor => activeColor ?? const Color(0xff0069cc); } diff --git a/app/lib/models/solution.dart b/app/lib/models/solution.dart deleted file mode 100644 index 14583ed..0000000 --- a/app/lib/models/solution.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'dart:convert'; - -import 'package:dde_gesture_manager/builder/provider_annotation.dart'; -import 'package:dde_gesture_manager/utils/helper.dart'; - -@ProviderModel(copyable: true) -class Solution { - @ProviderModelProp() - String? name; - - @ProviderModelProp() - String? description; - - @ProviderModelProp() - List? gestures; - - Solution.parse(solution) { - if (solution is String) solution = json.decode(solution); - assert(solution is Map); - name = solution['name']; - description = solution['desc']; - gestures = (solution['gestures'] as List? ?? []).map((ele) => GestureProp.parse(ele)).toList(); - } -} - -enum Gesture { - swipe, - tap, - pinch, -} - -enum GestureDirection { - up, - down, - left, - right, - pinch_in, - pinch_out, - none, -} - -enum GestureType { - built_in, - commandline, - shortcut, -} - -@ProviderModel(copyable: true) -class GestureProp { - @ProviderModelProp() - Gesture? gesture; - - @ProviderModelProp() - GestureDirection? direction; - - @ProviderModelProp() - int? fingers; - - @ProviderModelProp() - GestureType? type; - - @ProviderModelProp() - String? command; - - @ProviderModelProp() - String? remark; - - @override - bool operator ==(Object other) => - other is GestureProp && - other.gesture == this.gesture && - other.direction == this.direction && - other.fingers == this.fingers; - - @override - String toString() { - return 'GestureProp{gesture: $gesture, direction: $direction, fingers: $fingers, type: $type, command: $command, remark: $remark}'; - } - - GestureProp.empty(); - - GestureProp.parse(props) { - if (props is String) props = json.decode(props); - assert(props is Map); - gesture = H.getGestureByName(props['gesture']); - direction = H.getGestureDirectionByName(props['direction']); - fingers = props['fingers']; - type = H.getGestureTypeByName(props['type']); - command = props['command']; - remark = props['remark']; - } -} diff --git a/app/lib/pages/gesture_editor.dart b/app/lib/pages/gesture_editor.dart index 566ffe5..868d8b4 100644 --- a/app/lib/pages/gesture_editor.dart +++ b/app/lib/pages/gesture_editor.dart @@ -1,56 +1,32 @@ +import 'package:adaptive_scrollbar/adaptive_scrollbar.dart'; import 'package:dde_gesture_manager/constants/constants.dart'; import 'package:dde_gesture_manager/extensions.dart'; import 'package:dde_gesture_manager/models/content_layout.provider.dart'; -import 'package:dde_gesture_manager/models/solution.dart'; -import 'package:dde_gesture_manager/models/solution.provider.dart'; +import 'package:dde_gesture_manager/models/scheme.dart'; +import 'package:dde_gesture_manager/models/scheme.provider.dart'; +import 'package:dde_gesture_manager/models/settings.provider.dart'; import 'package:dde_gesture_manager/utils/helper.dart'; +import 'package:dde_gesture_manager/utils/keyboard_mapper.dart'; import 'package:dde_gesture_manager/widgets/dde_button.dart'; import 'package:dde_gesture_manager/widgets/dde_data_table.dart'; +import 'package:dde_gesture_manager/widgets/table_cell_shortcut_listener.dart'; +import 'package:dde_gesture_manager/widgets/table_cell_text_field.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +const double _headingRowHeight = 56; +const double _scrollBarWidth = 14; + class GestureEditor extends StatelessWidget { const GestureEditor({Key? key}) : super(key: key); - List _buildDataRow(List? gestures, BuildContext context) => (gestures ?? []) - .map((gesture) => DDataRow( - onSelectChanged: (selected) { - if (selected == true) - context.read().setProps( - gesture: gesture.gesture, - direction: gesture.direction, - fingers: gesture.fingers, - type: gesture.type, - command: gesture.command, - remark: gesture.remark, - ); - }, - selected: context.watch() == gesture, - cells: [ - Center( - child: Text('${LocaleKeys.gesture_editor_gestures}.${H.getGestureName(gesture.gesture)}').tr(), - ), - Center( - child: Text('${LocaleKeys.gesture_editor_directions}.${H.getGestureDirectionName(gesture.direction)}') - .tr()), - Center( - child: Text('${gesture.fingers}'), - ), - Center(child: Text('${LocaleKeys.gesture_editor_types}.${H.getGestureTypeName(gesture.type)}').tr()), - Text(gesture.command ?? ''), - Text(gesture.remark ?? ''), - ] - .map( - (ele) => DDataCell(ele), - ) - .toList(), - )) - .toList(); - @override Widget build(BuildContext context) { var layoutProvider = context.watch(); - var solutionProvider = context.watch(); + var schemeProvider = context.watch(); + final horizontalCtrl = ScrollController(); + final verticalCtrl = ScrollController(); + return Flexible( child: Padding( padding: const EdgeInsets.all(10), @@ -99,53 +75,81 @@ class GestureEditor extends StatelessWidget { ), Container(height: 10), Expanded( - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(defaultBorderRadius), - border: Border.all( - width: .2, - color: context.t.dividerColor, + child: GestureDetector( + onTap: () { + context.read().setProps(editMode: false); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(defaultBorderRadius), + border: Border.all( + width: .2, + color: context.t.dividerColor, + ), ), - ), - width: double.infinity, - clipBehavior: Clip.antiAlias, - child: LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) { - return Scrollbar( - isAlwaysShown: true, - child: SingleChildScrollView( - primary: true, - scrollDirection: Axis.horizontal, - child: ConstrainedBox( - constraints: BoxConstraints(minWidth: constraints.maxWidth), - child: DDataTable( - showCheckboxColumn: true, - headerBackgroundColor: context.t.dialogBackgroundColor, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(defaultBorderRadius), - border: Border.all( - width: .2, - color: context.t.dividerColor, + width: double.infinity, + clipBehavior: Clip.antiAlias, + child: LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) { + return AdaptiveScrollbar( + controller: verticalCtrl, + underColor: Colors.transparent, + sliderDecoration: BoxDecoration( + borderRadius: BorderRadius.circular(_scrollBarWidth / 2), + color: Colors.grey.withOpacity(.4), + ), + sliderActiveDecoration: BoxDecoration( + borderRadius: BorderRadius.circular(_scrollBarWidth / 2), + color: Colors.grey.withOpacity(.6), + ), + position: ScrollbarPosition.right, + underSpacing: EdgeInsets.only(top: _headingRowHeight), + width: _scrollBarWidth, + child: AdaptiveScrollbar( + width: _scrollBarWidth, + underColor: Colors.transparent, + sliderDecoration: BoxDecoration( + borderRadius: BorderRadius.circular(_scrollBarWidth / 2), + color: Colors.grey.withOpacity(.4), + ), + sliderActiveDecoration: BoxDecoration( + borderRadius: BorderRadius.circular(_scrollBarWidth / 2), + color: Colors.grey.withOpacity(.6), + ), + controller: horizontalCtrl, + position: ScrollbarPosition.bottom, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + controller: horizontalCtrl, + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: constraints.maxWidth), + child: DDataTable( + showBottomBorder: true, + headingRowHeight: _headingRowHeight, + showCheckboxColumn: true, + headerBackgroundColor: context.t.dialogBackgroundColor, + verticalScrollController: verticalCtrl, + dataRowColor: MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.hovered)) return context.t.dialogBackgroundColor; + if (states.contains(MaterialState.selected)) + return context.read().currentActiveColor; + return null; + }), + columns: [ + DDataColumn(label: Text(LocaleKeys.gesture_editor_fingers.tr()), center: true), + DDataColumn(label: Text(LocaleKeys.gesture_editor_gesture.tr()), center: true), + DDataColumn(label: Text(LocaleKeys.gesture_editor_direction.tr()), center: true), + DDataColumn(label: Text(LocaleKeys.gesture_editor_type.tr()), center: true), + DDataColumn(label: Text(LocaleKeys.gesture_editor_command.tr())), + DDataColumn(label: Text(LocaleKeys.gesture_editor_remark.tr())), + ], + rows: _buildDataRows(schemeProvider.gestures, context), ), ), - dataRowColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.hovered)) return context.t.dialogBackgroundColor; - if (states.contains(MaterialState.selected)) return Colors.blueAccent; - return null; - }), - columns: [ - DDataColumn(label: Text(LocaleKeys.gesture_editor_gesture.tr()), center: true), - DDataColumn(label: Text(LocaleKeys.gesture_editor_direction.tr()), center: true), - DDataColumn(label: Text(LocaleKeys.gesture_editor_fingers.tr()), center: true), - DDataColumn(label: Text(LocaleKeys.gesture_editor_type.tr()), center: true), - DDataColumn(label: Text(LocaleKeys.gesture_editor_command.tr())), - DDataColumn(label: Text(LocaleKeys.gesture_editor_remark.tr())), - ], - rows: _buildDataRow(solutionProvider.gestures, context), ), ), - ), - ); - }), + ); + }), + ), ), ), Container(height: 10), @@ -167,3 +171,261 @@ class GestureEditor extends StatelessWidget { ); } } + +List _buildDataRows(List? gestures, BuildContext context) => (gestures ?? []).map((gesture) { + var gesturePropProvider = context.watch(); + bool editing = gesturePropProvider == gesture && gesturePropProvider.editMode == true; + bool selected = gesturePropProvider == gesture && !editing; + return DDataRow( + onSelectChanged: (selected) { + var provider = context.read(); + if (selected == true) { + provider.setProps( + editMode: false, + ); + Future.microtask(() => provider.setProps( + id: gesture.id, + )); + } else if (selected == false) { + provider.onEditEnd = (prop) { + var schemeProvider = context.read(); + var newGestures = List.of(schemeProvider.gestures!); + var index = newGestures.indexWhere((element) => element == prop); + newGestures[index].copyFrom(prop); + context.read().setProps( + gestures: newGestures..sort(), + ); + }; + provider.copyFrom( + gesture..editMode = true, + ); + } + }, + selected: selected, + cells: editing ? _buildRowCellsEditing(context, gesture) : _buildRowCellsNormal(context, selected, gesture), + ); + }).toList(); + +List _buildRowCellsEditing(BuildContext context, GestureProp gesture) => [ + DButton.dropdown( + enabled: true, + child: DropdownButton( + icon: Icon(Icons.keyboard_arrow_down_rounded), + items: [3, 4, 5] + .map( + (e) => DropdownMenuItem( + child: Text('$e'), + value: e, + ), + ) + .toList(), + value: context.watch().fingers, + onChanged: (value) => context.read().setProps( + fingers: value, + editMode: true, + ), + isExpanded: true, + ), + ), + DButton.dropdown( + enabled: true, + width: 60.0, + child: DropdownButton( + icon: Icon(Icons.keyboard_arrow_down_rounded), + items: Gesture.values + .map( + (e) => DropdownMenuItem( + child: Text( + '${LocaleKeys.gesture_editor_gestures}.${H.getGestureName(e)}', + textScaleFactor: .8, + ).tr(), + value: e, + ), + ) + .toList(), + value: context.watch().gesture, + onChanged: (value) => context.read().setProps( + gesture: value, + editMode: true, + ), + isExpanded: true, + ), + ), + DButton.dropdown( + enabled: true, + width: 100.0, + child: DropdownButton( + icon: Icon(Icons.keyboard_arrow_down_rounded), + items: GestureDirection.values + .map( + (e) => DropdownMenuItem( + child: Text( + '${LocaleKeys.gesture_editor_directions}.${H.getGestureDirectionName(e)}', + textScaleFactor: .8, + ).tr(), + value: e, + ), + ) + .toList(), + value: context.watch().direction, + onChanged: (value) => context.read().setProps( + direction: value, + editMode: true, + ), + isExpanded: true, + ), + ), + DButton.dropdown( + enabled: true, + width: 100.0, + child: DropdownButton( + icon: Icon(Icons.keyboard_arrow_down_rounded), + items: GestureType.values + .map( + (e) => DropdownMenuItem( + child: Text( + '${LocaleKeys.gesture_editor_types}.${H.getGestureTypeName(e)}', + textScaleFactor: .8, + ).tr(), + value: e, + ), + ) + .toList(), + value: context.watch().type, + onChanged: (value) => context.read().setProps( + type: value, + command: '', + editMode: true, + ), + isExpanded: true, + ), + ), + _buildCommandCellsEditing(context), + TableCellTextField( + initText: gesture.remark, + hint: 'pls input cmd', + onComplete: (value) => context.read().setProps( + remark: value, + editMode: true, + ), + ), + ].map((e) => DDataCell(e)).toList(); + +Widget _buildCommandCellsEditing(BuildContext context) { + var gesture = context.read(); + switch (gesture.type) { + case GestureType.commandline: + return TableCellTextField( + initText: gesture.command, + hint: 'pls input cmd', + onComplete: (value) => context.read().setProps( + command: value, + editMode: true, + ), + ); + case GestureType.built_in: + return DButton.dropdown( + enabled: true, + width: 250.0, + child: DropdownButton( + icon: Icon(Icons.keyboard_arrow_down_rounded), + items: builtInCommands + .map( + (e) => DropdownMenuItem( + child: Text( + ('${LocaleKeys.built_in_commands}.$e').tr(), + textScaleFactor: .8, + ), + value: ('${LocaleKeys.built_in_commands}.$e').tr(), + ), + ) + .toList(), + value: + ('${LocaleKeys.built_in_commands}.${(builtInCommands.contains(gesture.command) ? gesture.command : builtInCommands.first)!}') + .tr(), + onChanged: (value) => context.read().setProps( + command: value, + editMode: true, + ), + isExpanded: true, + ), + ); + case GestureType.shortcut: + return TableCellShortcutListener( + width: 250.0, + initShortcut: gesture.command ?? '', + onComplete: (value) => context.read().setProps( + command: value, + editMode: true, + ), + ); + default: + throw Exception('Unknown gesture command type.'); + } +} + +List _buildRowCellsNormal(BuildContext context, bool selected, GestureProp gesture) => [ + Center( + child: Text( + '${gesture.fingers}', + ), + ), + Center( + child: Text( + '${LocaleKeys.gesture_editor_gestures}.${H.getGestureName(gesture.gesture)}', + ).tr(), + ), + Center( + child: Text( + '${LocaleKeys.gesture_editor_directions}.${H.getGestureDirectionName(gesture.direction)}', + ).tr()), + Center( + child: Text( + '${LocaleKeys.gesture_editor_types}.${H.getGestureTypeName(gesture.type)}', + ).tr()), + gesture.type == GestureType.shortcut + ? Row( + mainAxisAlignment: MainAxisAlignment.start, + children: (gesture.command ?? '').split('+').map( + (e) { + var keyNames = getPhysicalKeyNamesByRealName(e); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 2.0), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(defaultBorderRadius / 2), + color: context.t.dialogBackgroundColor, + border: Border.all( + width: 1, + color: Color(0xff565656), + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + child: Text( + keyNames != null ? keyNames.displayName : LocaleKeys.str_null.tr(), + style: TextStyle( + color: context.watch().currentActiveColor, + ), + ), + ), + ), + ); + }, + ).toList(), + ) + : Text( + gesture.command ?? '', + ), + Text( + gesture.remark ?? '', + ), + ] + .map( + (ele) => DDataCell(DefaultTextStyle( + style: context.t.textTheme.bodyText2!.copyWith( + color: selected ? Colors.white : null, + ), + child: ele)), + ) + .toList(); diff --git a/app/lib/pages/home.dart b/app/lib/pages/home.dart index 26341bc..50d1f8c 100644 --- a/app/lib/pages/home.dart +++ b/app/lib/pages/home.dart @@ -1,6 +1,6 @@ import 'package:dde_gesture_manager/extensions.dart'; -import 'package:dde_gesture_manager/models/local_solutions_provider.dart'; -import 'package:dde_gesture_manager/models/solution.provider.dart'; +import 'package:dde_gesture_manager/models/local_schemes_provider.dart'; +import 'package:dde_gesture_manager/models/scheme.provider.dart'; import 'package:dde_gesture_manager/pages/content.dart'; import 'package:dde_gesture_manager/pages/footer.dart'; import 'package:flutter/material.dart'; @@ -18,7 +18,7 @@ class _HomePageState extends State { return Scaffold( body: MultiProvider( providers: [ - ChangeNotifierProvider(create: (context) => SolutionProvider.parse(''' + ChangeNotifierProvider(create: (context) => SchemeProvider.parse(''' { "name": "test", "desc": "some desc", @@ -66,7 +66,7 @@ class _HomePageState extends State { } ''')), ChangeNotifierProvider(create: (context) => GesturePropProvider.empty()), - ChangeNotifierProvider(create: (context) => LocalSolutionsProvider(),lazy: false), + ChangeNotifierProvider(create: (context) => LocalSchemesProvider(),lazy: false), ], child: Column( mainAxisSize: MainAxisSize.max, diff --git a/app/lib/pages/local_manager.dart b/app/lib/pages/local_manager.dart index 7fcab24..137ba5e 100644 --- a/app/lib/pages/local_manager.dart +++ b/app/lib/pages/local_manager.dart @@ -1,8 +1,9 @@ import 'package:dde_gesture_manager/constants/constants.dart'; import 'package:dde_gesture_manager/extensions.dart'; import 'package:dde_gesture_manager/models/content_layout.provider.dart'; -import 'package:dde_gesture_manager/models/local_solutions_provider.dart'; -import 'package:dde_gesture_manager/models/solution.provider.dart'; +import 'package:dde_gesture_manager/models/local_schemes_provider.dart'; +import 'package:dde_gesture_manager/models/scheme.provider.dart'; +import 'package:dde_gesture_manager/models/settings.provider.dart'; import 'package:dde_gesture_manager/widgets/dde_button.dart'; import 'package:flutter/animation.dart'; import 'package:flutter/cupertino.dart'; @@ -21,25 +22,28 @@ class LocalManager extends StatefulWidget { class _LocalManagerState extends State { late ScrollController _scrollController; int? _hoveringIndex; - int? _selectedIndex; + late int _selectedIndex; @override void initState() { super.initState(); + + /// todo: load from sp + _selectedIndex = 0; _scrollController = ScrollController(); } Color _getItemBackgroundColor(int index) { Color _color = index % 2 == 0 ? context.t.scaffoldBackgroundColor : context.t.backgroundColor; if (index == _hoveringIndex) _color = context.t.scaffoldBackgroundColor; - if (index == _selectedIndex) _color = Colors.blueAccent; + if (index == _selectedIndex) _color = context.read().currentActiveColor; return _color; } @override Widget build(BuildContext context) { var isOpen = context.watch().localManagerOpened == true; - var localSolutions = context.watch().solutions ?? []; + var localSchemes = context.watch().schemes ?? []; return AnimatedContainer( duration: mediumDuration, curve: Curves.easeInOut, @@ -83,54 +87,82 @@ class _LocalManagerState extends State { ], ), Flexible( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: ListView.builder( - controller: _scrollController, - itemBuilder: (context, index) => GestureDetector( - onDoubleTap: () { - context.read().copyFrom(localSolutions[index].solution); - setState(() { - _selectedIndex = index; - }); - }, - onTap: () { - setState(() { - _selectedIndex = index; - }); - }, - child: MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (_) { - setState(() { - _hoveringIndex = index; - }); - }, - child: Container( - color: _getItemBackgroundColor(index), - child: Padding( - padding: const EdgeInsets.only(right: 12.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(localSolutions[index].solution.name ?? ''), - Text('456'), - ], + child: Padding( + padding: EdgeInsets.only(top: 5), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Container( + decoration: BoxDecoration( + border: Border.all( + width: .3, + color: context.t.dividerColor, + ), + borderRadius: BorderRadius.circular(defaultBorderRadius), + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 1, vertical: 2), + child: ListView.builder( + controller: _scrollController, + itemBuilder: (context, index) => GestureDetector( + onTap: () { + context.read().copyFrom(localSchemes[index].scheme); + setState(() { + _selectedIndex = index; + }); + }, + child: MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) { + setState(() { + _hoveringIndex = index; + }); + }, + child: Container( + color: _getItemBackgroundColor(index), + child: Padding( + padding: const EdgeInsets.only(left: 6, right: 12.0), + child: DefaultTextStyle( + style: context.t.textTheme.bodyText2!.copyWith( + color: _selectedIndex == index ? Colors.white : null, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(localSchemes[index].scheme.name ?? ''), + Text('456'), + ], + ), + ), + ), + ), ), ), + itemCount: localSchemes.length, ), ), ), - itemCount: localSolutions.length, ), - ), - Container( - height: 150, - color: Colors.black, - ), - ], + Container(height: 5), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + DButton.add(enabled: true), + DButton.delete(enabled: _selectedIndex > 0), + DButton.duplicate(enabled: _selectedIndex > 0), + DButton.apply(enabled: _selectedIndex > 0), + ] + .map((e) => Padding( + padding: const EdgeInsets.only(right: 4), + child: e, + )) + .toList(), + ), + ), + ], + ), ), ), ], diff --git a/app/lib/themes/dark.dart b/app/lib/themes/dark.dart index 5746339..1d3f280 100644 --- a/app/lib/themes/dark.dart +++ b/app/lib/themes/dark.dart @@ -18,9 +18,20 @@ var darkTheme = ThemeData.dark().copyWith( ), ), popupMenuTheme: ThemeData.dark().popupMenuTheme.copyWith( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(defaultBorderRadius), - ), - ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(defaultBorderRadius), + ), + ), dialogBackgroundColor: Color(0xff202020), + tooltipTheme: ThemeData.dark().tooltipTheme.copyWith( + textStyle: TextStyle( + color: Colors.grey, + ), + padding: EdgeInsets.symmetric(horizontal: 3, vertical: 2), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Color(0xff282828).withOpacity(.9), + border: Border.all(color: Colors.black38), + ), + ), ); diff --git a/app/lib/themes/light.dart b/app/lib/themes/light.dart index ffeee59..fc0b49c 100644 --- a/app/lib/themes/light.dart +++ b/app/lib/themes/light.dart @@ -23,4 +23,15 @@ var lightTheme = ThemeData.light().copyWith( ), ), dialogBackgroundColor: Color(0xfffefefe), + tooltipTheme: ThemeData.dark().tooltipTheme.copyWith( + textStyle: TextStyle( + color: Colors.grey.shade600, + ), + padding: EdgeInsets.symmetric(horizontal: 3, vertical: 2), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Color(0xfff8f8f8).withOpacity(.9), + border: Border.all(color: Colors.grey.shade400), + ), + ), ); diff --git a/app/lib/utils/helper.dart b/app/lib/utils/helper.dart index c510eac..4b891ef 100644 --- a/app/lib/utils/helper.dart +++ b/app/lib/utils/helper.dart @@ -1,5 +1,5 @@ import 'package:dde_gesture_manager/models/content_layout.provider.dart'; -import 'package:dde_gesture_manager/models/solution.dart'; +import 'package:dde_gesture_manager/models/scheme.dart'; import 'package:flutter/cupertino.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:dde_gesture_manager/constants/constants.dart'; @@ -96,7 +96,15 @@ class H { 'shortcut': GestureType.shortcut, 'commandline': GestureType.commandline, }[typeName] ?? - GestureType.built_in; + GestureType.built_in; + + static Color? parseQtActiveColor(String? inp) { + if (inp == null) return null; + var list = inp.split(','); + if (list.length != 4) return null; + var rgba = list.map((e) => int.parse(e) ~/ 257).toList(); + return Color.fromARGB(rgba[3], rgba[0], rgba[1], rgba[2]); + } } class PreferredPanelsStatus { diff --git a/app/lib/utils/init_linux.dart b/app/lib/utils/init_linux.dart index ca8faa7..3544dfd 100644 --- a/app/lib/utils/init_linux.dart +++ b/app/lib/utils/init_linux.dart @@ -18,8 +18,11 @@ Future initEvents(BuildContext context) async { } else { var xsettings = GSettings('com.deepin.xsettings'); String? themeName; + Color? activeColor; try { themeName = (await xsettings.get('theme-name')).toString(); + var _activeColor = (await xsettings.get('qt-active-color')); + activeColor = H.parseQtActiveColor(_activeColor.toNative()); } catch (e) { print(e); context.read().setProps(isDarkMode: false); @@ -27,10 +30,14 @@ Future initEvents(BuildContext context) async { if (themeName != null) { context.read().setProps(isDarkMode: themeName.contains('dark')); + context.read().setProps(activeColor: activeColor); xsettings.keysChanged.listen((event) { xsettings.get('theme-name').then((value) { context.read().setProps(isDarkMode: value.toString().contains('dark')); }); + xsettings.get('qt-active-color').then((value) { + context.read().setProps(activeColor: H.parseQtActiveColor(value.toNative())); + }); }); } } diff --git a/app/lib/utils/keyboard_mapper.dart b/app/lib/utils/keyboard_mapper.dart new file mode 100644 index 0000000..b685232 --- /dev/null +++ b/app/lib/utils/keyboard_mapper.dart @@ -0,0 +1,405 @@ +import 'package:collection/collection.dart'; + +const KeysOrder = [ + 'Ctrl', + 'Super', + 'Alt', + 'Shift', + 'Return', + 'Escape', + 'BackSpace', + 'Tab', + 'space', + '→', + '←', + '↓', + '↑', + 'Print', + 'ScrollLock', + 'Pause', + 'Insert', + 'Home', + 'Page_Up', + 'Delete', + 'End', + 'Page_Down', + 'F1', + 'F2', + 'F3', + 'F4', + 'F5', + 'F6', + 'F7', + 'F8', + 'F9', + 'F10', + 'F11', + 'F12', + '-', + '=', + '(', + ')', + r'\', + ';', + "'", + '`', + ',', + '.', + '/', + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '0', +]; + +class KeyNames implements Comparable { + final String displayName; + final String realName; + + const KeyNames({required this.displayName, required this.realName}); + + @override + int compareTo(other) => KeysOrder.indexOf(this.displayName) - KeysOrder.indexOf(other.displayName); + + @override + String toString() => realName; +} + +KeyNames? getPhysicalKeyNames(int keyId) => _knownPhysicalKeyNames[keyId]; + +KeyNames? getPhysicalKeyNamesByRealName(String name) => + _knownPhysicalKeyNames.values.firstWhereOrNull((element) => element.realName == name); + +/// https://www.w3.org/TR/uievents-code/ +const Map _knownPhysicalKeyNames = { + /// https://www.w3.org/TR/uievents-code/#key-legacy + /*0x00000010: hyper, + 0x00000011: superKey, + 0x00000016: turbo, + 0x0007009b: abort, + 0x00000015: resume, + 0x00000014: suspend, + 0x00070079: again, + 0x0007007c: copy, + 0x0007007b: cut, + 0x0007007e: find, + 0x00070074: open, + 0x0007007d: paste, + 0x000700a3: props, + 0x00070077: select, + 0x0007007a: undo,*/ + + /// Often handled in hardware so that events aren't generated for this key. + /*0x00000012: fn, + 0x00000013: fnLock,*/ + + /// W.T.F + /*0x00000017: privacyScreenToggle, + 0x00010082: sleep, + 0x00010083: wakeUp, + 0x000100b5: displayToggleIntExt, + 0x0005ff01: gameButton1, + 0x0005ff02: gameButton2, + 0x0005ff03: gameButton3, + 0x0005ff04: gameButton4, + 0x0005ff05: gameButton5, + 0x0005ff06: gameButton6, + 0x0005ff07: gameButton7, + 0x0005ff08: gameButton8, + 0x0005ff09: gameButton9, + 0x0005ff0a: gameButton10, + 0x0005ff0b: gameButton11, + 0x0005ff0c: gameButton12, + 0x0005ff0d: gameButton13, + 0x0005ff0e: gameButton14, + 0x0005ff0f: gameButton15, + 0x0005ff10: gameButton16, + 0x0005ff11: gameButtonA, + 0x0005ff12: gameButtonB, + 0x0005ff13: gameButtonC, + 0x0005ff14: gameButtonLeft1, + 0x0005ff15: gameButtonLeft2, + 0x0005ff16: gameButtonMode, + 0x0005ff17: gameButtonRight1, + 0x0005ff18: gameButtonRight2, + 0x0005ff19: gameButtonSelect, + 0x0005ff1a: gameButtonStart, + 0x0005ff1b: gameButtonThumbLeft, + 0x0005ff1c: gameButtonThumbRight, + 0x0005ff1d: gameButtonX, + 0x0005ff1e: gameButtonY, + 0x0005ff1f: gameButtonZ, + 0x00070000: usbReserved, + 0x00070001: usbErrorRollOver, + 0x00070002: usbPostFail, + 0x00070003: usbErrorUndefined,*/ + + 0x00070004: const KeyNames(displayName: 'A', realName: 'a'), + 0x00070005: const KeyNames(displayName: 'B', realName: 'b'), + 0x00070006: const KeyNames(displayName: 'C', realName: 'c'), + 0x00070007: const KeyNames(displayName: 'D', realName: 'd'), + 0x00070008: const KeyNames(displayName: 'E', realName: 'e'), + 0x00070009: const KeyNames(displayName: 'F', realName: 'f'), + 0x0007000a: const KeyNames(displayName: 'G', realName: 'g'), + 0x0007000b: const KeyNames(displayName: 'H', realName: 'h'), + 0x0007000c: const KeyNames(displayName: 'I', realName: 'i'), + 0x0007000d: const KeyNames(displayName: 'J', realName: 'j'), + 0x0007000e: const KeyNames(displayName: 'K', realName: 'k'), + 0x0007000f: const KeyNames(displayName: 'L', realName: 'l'), + 0x00070010: const KeyNames(displayName: 'M', realName: 'm'), + 0x00070011: const KeyNames(displayName: 'N', realName: 'n'), + 0x00070012: const KeyNames(displayName: 'O', realName: 'o'), + 0x00070013: const KeyNames(displayName: 'P', realName: 'p'), + 0x00070014: const KeyNames(displayName: 'Q', realName: 'q'), + 0x00070015: const KeyNames(displayName: 'R', realName: 'r'), + 0x00070016: const KeyNames(displayName: 'S', realName: 's'), + 0x00070017: const KeyNames(displayName: 'T', realName: 't'), + 0x00070018: const KeyNames(displayName: 'U', realName: 'u'), + 0x00070019: const KeyNames(displayName: 'V', realName: 'v'), + 0x0007001a: const KeyNames(displayName: 'W', realName: 'w'), + 0x0007001b: const KeyNames(displayName: 'X', realName: 'x'), + 0x0007001c: const KeyNames(displayName: 'Y', realName: 'y'), + 0x0007001d: const KeyNames(displayName: 'Z', realName: 'z'), + 0x0007001e: const KeyNames(displayName: '1', realName: '1'), + 0x0007001f: const KeyNames(displayName: '2', realName: '2'), + 0x00070020: const KeyNames(displayName: '3', realName: '3'), + 0x00070021: const KeyNames(displayName: '4', realName: '4'), + 0x00070022: const KeyNames(displayName: '5', realName: '5'), + 0x00070023: const KeyNames(displayName: '6', realName: '6'), + 0x00070024: const KeyNames(displayName: '7', realName: '7'), + 0x00070025: const KeyNames(displayName: '8', realName: '8'), + 0x00070026: const KeyNames(displayName: '9', realName: '9'), + 0x00070027: const KeyNames(displayName: '0', realName: '0'), + 0x00070028: const KeyNames(displayName: 'Return', realName: 'Return'), + 0x00070029: const KeyNames(displayName: 'Escape', realName: 'Escape'), + 0x0007002a: const KeyNames(displayName: 'BackSpace', realName: 'BackSpace'), + 0x0007002b: const KeyNames(displayName: 'Tab', realName: 'Tab'), + 0x0007002c: const KeyNames(displayName: 'space', realName: 'space'), + 0x0007002d: const KeyNames(displayName: '-', realName: 'minus'), + 0x0007002e: const KeyNames(displayName: '=', realName: 'equal'), + 0x0007002f: const KeyNames(displayName: '(', realName: 'parenleft'), + 0x00070030: const KeyNames(displayName: ')', realName: 'parenright'), + 0x00070031: const KeyNames(displayName: r'\', realName: 'backslash'), + 0x00070033: const KeyNames(displayName: ';', realName: 'semicolon'), + 0x00070034: const KeyNames(displayName: "'", realName: 'apostrophe'), + 0x00070035: const KeyNames(displayName: '`', realName: 'grave'), + 0x00070036: const KeyNames(displayName: ',', realName: 'comma'), + 0x00070037: const KeyNames(displayName: '.', realName: 'period'), + 0x00070038: const KeyNames(displayName: '/', realName: 'slash'), + 0x00070039: const KeyNames(displayName: 'CapsLock', realName: 'Caps_Lock'), + 0x0007003a: const KeyNames(displayName: 'F1', realName: 'F1'), + 0x0007003b: const KeyNames(displayName: 'F2', realName: 'F2'), + 0x0007003c: const KeyNames(displayName: 'F3', realName: 'F3'), + 0x0007003d: const KeyNames(displayName: 'F4', realName: 'F4'), + 0x0007003e: const KeyNames(displayName: 'F5', realName: 'F5'), + 0x0007003f: const KeyNames(displayName: 'F6', realName: 'F6'), + 0x00070040: const KeyNames(displayName: 'F7', realName: 'F7'), + 0x00070041: const KeyNames(displayName: 'F8', realName: 'F8'), + 0x00070042: const KeyNames(displayName: 'F9', realName: 'F9'), + 0x00070043: const KeyNames(displayName: 'F10', realName: 'F10'), + 0x00070044: const KeyNames(displayName: 'F11', realName: 'F11'), + 0x00070045: const KeyNames(displayName: 'F12', realName: 'F12'), + 0x00070046: const KeyNames(displayName: 'Print', realName: 'Print'), + 0x00070047: const KeyNames(displayName: 'ScrollLock', realName: 'Scroll_Lock'), + 0x00070048: const KeyNames(displayName: 'Pause', realName: 'Pause'), + 0x00070049: const KeyNames(displayName: 'Insert', realName: 'Insert'), + 0x0007004a: const KeyNames(displayName: 'Home', realName: 'Home'), + 0x0007004b: const KeyNames(displayName: 'Page_Up', realName: 'Page_Up'), + 0x0007004c: const KeyNames(displayName: 'Delete', realName: 'Delete'), + 0x0007004d: const KeyNames(displayName: 'End', realName: 'End'), + 0x0007004e: const KeyNames(displayName: 'Page_Down', realName: 'Page_Down'), + 0x0007004f: const KeyNames(displayName: '→', realName: 'Right'), + 0x00070050: const KeyNames(displayName: '←', realName: 'Left'), + 0x00070051: const KeyNames(displayName: '↓', realName: 'Down'), + 0x00070052: const KeyNames(displayName: '↑', realName: 'Up'), + 0x00070053: const KeyNames(displayName: 'NumLock', realName: 'Num_Lock'), + 0x00070054: const KeyNames(displayName: 'KP_Divide', realName: 'KP_Divide'), + 0x00070055: const KeyNames(displayName: 'KP_Multiply', realName: 'KP_Multiply'), + 0x00070056: const KeyNames(displayName: 'KP_Subtract', realName: 'KP_Subtract'), + 0x00070057: const KeyNames(displayName: 'KP_Add', realName: 'KP_Add'), + 0x00070058: const KeyNames(displayName: 'KP_Enter', realName: 'KP_Enter'), + 0x00070059: const KeyNames(displayName: 'KP_1', realName: 'KP_1'), + 0x0007005a: const KeyNames(displayName: 'KP_2', realName: 'KP_2'), + 0x0007005b: const KeyNames(displayName: 'KP_3', realName: 'KP_3'), + 0x0007005c: const KeyNames(displayName: 'KP_4', realName: 'KP_4'), + 0x0007005d: const KeyNames(displayName: 'KP_5', realName: 'KP_5'), + 0x0007005e: const KeyNames(displayName: 'KP_6', realName: 'KP_6'), + 0x0007005f: const KeyNames(displayName: 'KP_7', realName: 'KP_7'), + 0x00070060: const KeyNames(displayName: 'KP_8', realName: 'KP_8'), + 0x00070061: const KeyNames(displayName: 'KP_9', realName: 'KP_9'), + 0x00070062: const KeyNames(displayName: 'KP_0', realName: 'KP_0'), + 0x00070063: const KeyNames(displayName: '.', realName: 'KP_Decimal'), + + /// IntlBackslash ??? + 0x00070064: const KeyNames(displayName: r'\', realName: 'backslash'), + 0x00070065: const KeyNames(displayName: 'Menu', realName: 'Menu'), + + /// Mac only + // 0x00070066: power, + 0x00070067: const KeyNames(displayName: 'KP_Equal', realName: 'KP_Equal'), + 0x00070068: const KeyNames(displayName: 'F13', realName: 'F13'), + 0x00070069: const KeyNames(displayName: 'F14', realName: 'F14'), + 0x0007006a: const KeyNames(displayName: 'F15', realName: 'F15'), + 0x0007006b: const KeyNames(displayName: 'F16', realName: 'F16'), + 0x0007006c: const KeyNames(displayName: 'F17', realName: 'F17'), + 0x0007006d: const KeyNames(displayName: 'F18', realName: 'F18'), + 0x0007006e: const KeyNames(displayName: 'F19', realName: 'F19'), + 0x0007006f: const KeyNames(displayName: 'F20', realName: 'F20'), + 0x00070070: const KeyNames(displayName: 'F21', realName: 'F21'), + 0x00070071: const KeyNames(displayName: 'F22', realName: 'F22'), + 0x00070072: const KeyNames(displayName: 'F23', realName: 'F23'), + 0x00070073: const KeyNames(displayName: 'F24', realName: 'F24'), + + /// Not present on standard PC keyboards. + // 0x00070075: help, + 0x0007007f: const KeyNames(displayName: 'XF86AudioMute', realName: 'XF86AudioMute'), + 0x00070080: const KeyNames(displayName: 'XF86AudioRaiseVolume', realName: 'XF86AudioRaiseVolume'), + 0x00070081: const KeyNames(displayName: 'XF86AudioLowerVolume', realName: 'XF86AudioLowerVolume'), + 0x00070085: const KeyNames(displayName: 'KP_Separator', realName: 'KP_Separator'), + 0x00070087: const KeyNames(displayName: 'KanaRo', realName: 'kana_RO'), + 0x00070088: const KeyNames(displayName: 'Hiragana_Katakana', realName: 'Hiragana_Katakana'), + 0x00070089: const KeyNames(displayName: 'yen', realName: 'yen'), + 0x0007008a: const KeyNames(displayName: 'Henkan_Mode', realName: 'Henkan_Mode'), + 0x0007008b: const KeyNames(displayName: 'Muhenkan', realName: 'Muhenkan'), + 0x00070090: const KeyNames(displayName: 'Hangul', realName: 'Hangul'), + 0x00070091: const KeyNames(displayName: 'Hangul_Hanja', realName: 'Hangul_Hanja'), + 0x00070092: const KeyNames(displayName: 'Katakana', realName: 'Katakana'), + 0x00070093: const KeyNames(displayName: 'Hiragana', realName: 'Hiragana'), + 0x00070094: const KeyNames(displayName: 'Zenkaku', realName: 'Zenkaku'), + 0x000700b6: const KeyNames(displayName: 'KP_Left', realName: 'KP_Left'), + 0x000700b7: const KeyNames(displayName: 'KP_Right', realName: 'KP_Right'), + + /// Found on the Microsoft Natural Keyboard. + // 0x000700bb: numpadBackspace, + + /// W.T.F ??? + /*0x000700d0: numpadMemoryStore, + 0x000700d1: numpadMemoryRecall, + 0x000700d2: numpadMemoryClear, + 0x000700d3: numpadMemoryAdd, + 0x000700d4: numpadMemorySubtract, + 0x000700d7: numpadSignChange, + 0x000700d9: numpadClearEntry,*/ + 0x000700d8: const KeyNames(displayName: 'NumLock', realName: 'Num_Lock'), + 0x000700e0: const KeyNames(displayName: 'Ctrl', realName: 'Control_L'), + 0x000700e1: const KeyNames(displayName: 'Shift', realName: 'Shift_L'), + 0x000700e2: const KeyNames(displayName: 'Alt', realName: 'Alt_L'), + 0x000700e3: const KeyNames(displayName: 'Super', realName: 'Super_L'), + 0x000700e4: const KeyNames(displayName: 'Ctrl', realName: 'Control_R'), + 0x000700e5: const KeyNames(displayName: 'Shift', realName: 'Shift_R'), + 0x000700e6: const KeyNames(displayName: 'Alt', realName: 'Alt_R'), + 0x000700e7: const KeyNames(displayName: 'Super', realName: 'Super_R'), + + /// Toggles the display of information about the currently selected content, program, or media. + // 0x000c0060: info, + + /// Toggles closed captioning on and off. + // 0x000c0061: closedCaptionToggle, + + /// Too dangerous ... + /*0x000c006f: brightnessUp, + 0x000c0070: brightnessDown, + 0x000c0072: brightnessToggle, + 0x000c0073: brightnessMinimum, + 0x000c0074: brightnessMaximum, + 0x000c0075: brightnessAuto,*/ + + /// seems only works on mobile. + /*0x000c0079: kbdIllumUp, + 0x000c007a: kbdIllumDown, + 0x000c0083: mediaLast, + 0x000c008c: launchPhone, + 0x000c008d: programGuide, + 0x000c0094: exit, + 0x000c009c: channelUp, + 0x000c009d: channelDown, + 0x000c00b0: mediaPlay, + 0x000c00b1: mediaPause, + 0x000c00b2: mediaRecord, + 0x000c00b3: mediaFastForward, + 0x000c00b4: mediaRewind, + 0x000c00b5: mediaTrackNext, + 0x000c00b6: mediaTrackPrevious, + 0x000c00b7: mediaStop, + + /// Mac only + // 0x000c00b8: eject, + 0x000c00cd: mediaPlayPause, + 0x000c00cf: speechInputToggle, + 0x000c00e5: bassBoost, + 0x000c0183: mediaSelect,*/ + 0x000c0184: const KeyNames(displayName: 'XF86Word', realName: 'XF86Word'), + 0x000c0186: const KeyNames(displayName: 'XF86Excel', realName: 'XF86Excel'), + 0x000c018a: const KeyNames(displayName: 'XF86Mail', realName: 'XF86Mail'), + + /// not implements on linux. + // 0x000c018d: launchContacts, + 0x000c018e: const KeyNames(displayName: 'XF86Calendar', realName: 'XF86Calendar'), + 0x000c0192: const KeyNames(displayName: 'XF86Launch2', realName: 'XF86Launch2'), + 0x000c0194: const KeyNames(displayName: 'XF86Launch1', realName: 'XF86Launch1'), + + /// I don't think need implement those keys + /*0x000c0196: launchInternetBrowser, + 0x000c019c: logOff, + 0x000c019e: lockScreen, + 0x000c019f: launchControlPanel, + 0x000c01a2: selectTask, + 0x000c01a7: launchDocuments, + 0x000c01ab: spellCheck, + 0x000c01ae: launchKeyboardLayout, + 0x000c01b1: launchScreenSaver, + 0x000c01b7: launchAudioBrowser, + 0x000c01cb: launchAssistant, + 0x000c0201: newKey, + 0x000c0203: close, + 0x000c0207: save, + 0x000c0208: print, + 0x000c0221: browserSearch, + 0x000c0223: browserHome, + 0x000c0224: browserBack, + 0x000c0225: browserForward, + 0x000c0226: browserStop, + 0x000c0227: browserRefresh, + 0x000c022a: browserFavorites, + 0x000c022d: zoomIn, + 0x000c022e: zoomOut, + 0x000c0232: zoomToggle, + 0x000c0279: redo, + 0x000c0289: mailReply, + 0x000c028b: mailForward, + 0x000c028c: mailSend, + 0x000c029d: keyboardLayoutSelect, + 0x000c029f: showAllWindows,*/ +}; diff --git a/app/lib/widgets/dde_button.dart b/app/lib/widgets/dde_button.dart index 70b87b9..77a00d6 100644 --- a/app/lib/widgets/dde_button.dart +++ b/app/lib/widgets/dde_button.dart @@ -1,4 +1,6 @@ import 'package:dde_gesture_manager/constants/constants.dart'; +import 'package:dde_gesture_manager/extensions.dart'; +import 'package:dde_gesture_manager/models/settings.provider.dart'; import 'package:flutter/material.dart'; import 'package:glass_kit/glass_kit.dart'; @@ -7,6 +9,8 @@ class DButton extends StatefulWidget { final double height; final Widget child; final GestureTapCallback? onTap; + final EdgeInsets? padding; + final Color? activeBorderColor; const DButton({ Key? key, @@ -14,8 +18,94 @@ class DButton extends StatefulWidget { this.height = defaultButtonHeight, required this.child, this.onTap, + this.padding, + this.activeBorderColor, }) : super(key: key); + factory DButton.add({ + Key? key, + required enabled, + GestureTapCallback? onTap, + height = defaultButtonHeight * .7, + width = defaultButtonHeight * .7, + }) => + DButton( + key: key, + width: width, + height: height, + onTap: onTap, + child: Tooltip( + child: Opacity(opacity: enabled ? 1 : 0.4, child: const Icon(Icons.add, size: 18)), + message: LocaleKeys.operation_add.tr(), + )); + + factory DButton.delete({ + Key? key, + required enabled, + GestureTapCallback? onTap, + height = defaultButtonHeight * .7, + width = defaultButtonHeight * .7, + }) => + DButton( + key: key, + width: width, + height: height, + onTap: onTap, + child: Tooltip( + child: Opacity(opacity: enabled ? 1 : 0.4, child: const Icon(Icons.remove, size: 18)), + message: LocaleKeys.operation_delete.tr(), + )); + + factory DButton.apply({ + Key? key, + required enabled, + GestureTapCallback? onTap, + height = defaultButtonHeight * .7, + width = defaultButtonHeight * .7, + }) => + DButton( + key: key, + width: width, + height: height, + onTap: onTap, + child: Tooltip( + child: Opacity(opacity: enabled ? 1 : 0.4, child: const Icon(Icons.check, size: 18)), + message: LocaleKeys.operation_apply.tr(), + )); + + factory DButton.duplicate({ + Key? key, + required enabled, + GestureTapCallback? onTap, + height = defaultButtonHeight * .7, + width = defaultButtonHeight * .7, + }) => + DButton( + key: key, + width: width, + height: height, + onTap: onTap, + child: Tooltip( + child: Opacity(opacity: enabled ? 1 : 0.4, child: const Icon(Icons.copy_rounded, size: 18)), + message: LocaleKeys.operation_duplicate.tr(), + )); + + factory DButton.dropdown({ + Key? key, + width = 60.0, + height = kMinInteractiveDimension * .86, + padding: const EdgeInsets.only(left: 15), + required enabled, + required DropdownButton child, + }) => + DButton( + key: key, + width: width, + height: height, + padding: padding, + child: child, + ); + @override State createState() => _DButtonState(); } @@ -26,8 +116,9 @@ class _DButtonState extends State { @override Widget build(BuildContext context) { return GestureDetector( - onTap: widget.onTap, + onTap: widget.child is DropdownButton ? (widget.child as DropdownButton).onTap : widget.onTap, child: GlassContainer( + padding: widget.padding, width: widget.width, height: widget.height, gradient: LinearGradient( @@ -35,8 +126,10 @@ class _DButtonState extends State { begin: Alignment.topCenter, end: Alignment.bottomCenter, ), - borderColor: Color(0xff565656), - borderWidth: 1, + borderColor: _hovering + ? (widget.activeBorderColor ?? context.watch().currentActiveColor) + : Color(0xff565656), + borderWidth: 2, borderRadius: BorderRadius.circular(defaultBorderRadius), child: MouseRegion( onEnter: (event) => setState(() { diff --git a/app/lib/widgets/dde_data_table.dart b/app/lib/widgets/dde_data_table.dart index b1ebe2b..5a7b90a 100644 --- a/app/lib/widgets/dde_data_table.dart +++ b/app/lib/widgets/dde_data_table.dart @@ -420,6 +420,7 @@ class DDataCell { /// * class DDataTable extends StatefulWidget { final Color headerBackgroundColor; + final ScrollController verticalScrollController; /// Creates a widget describing a data table. /// @@ -466,6 +467,7 @@ class DDataTable extends StatefulWidget { required this.rows, this.checkboxHorizontalMargin, required this.headerBackgroundColor, + required this.verticalScrollController, }) : assert(columns != null), assert(columns.isNotEmpty), assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)), @@ -755,7 +757,7 @@ class _DDataTableState extends State { textDirection: numeric ? TextDirection.rtl : null, mainAxisAlignment: center ? MainAxisAlignment.spaceAround : MainAxisAlignment.start, children: [ - label, + Flexible(child: label), if (onSort != null) ...[ _SortArrow( visible: sorted, @@ -1033,6 +1035,7 @@ class _DDataTableState extends State { padding: EdgeInsets.only(top: _headersRect?.last.height ?? 0), child: SingleChildScrollView( scrollDirection: Axis.vertical, + controller: widget.verticalScrollController, child: Transform.translate( offset: Offset(0, -(_headersRect?.last.height ?? 0)), child: Table( diff --git a/app/lib/widgets/language_switcher.dart b/app/lib/widgets/language_switcher.dart index 50c7df1..e98317d 100644 --- a/app/lib/widgets/language_switcher.dart +++ b/app/lib/widgets/language_switcher.dart @@ -1,13 +1,14 @@ -import 'package:collection/collection.dart'; import 'package:dde_gesture_manager/constants/sp_keys.dart'; import 'package:dde_gesture_manager/constants/supported_locales.dart'; import 'package:dde_gesture_manager/extensions.dart'; +import 'package:dde_gesture_manager/generated/codegen_loader.g.dart'; import 'package:dde_gesture_manager/generated/locale_keys.g.dart'; +import 'package:dde_gesture_manager/models/local_schemes_provider.dart'; import 'package:dde_gesture_manager/utils/helper.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:window_manager/window_manager.dart'; +import 'package:easy_localization/src/translations.dart'; class LanguageSwitcher extends StatelessWidget { const LanguageSwitcher({Key? key}) : super(key: key); @@ -15,10 +16,10 @@ class LanguageSwitcher extends StatelessWidget { @override Widget build(BuildContext context) { var _locale = EasyLocalization.of(context)?.currentLocale; - var _supportedLocale = supportedLocales.firstWhereOrNull((element) => element == _locale); + var _supportedLocale = getSupportedLocale(_locale) ?? SupportedLocale.zh_CN; return PopupMenuButton( - tooltip: LocaleKeys.language_tip.tr(), + tooltip: LocaleKeys.language_tip.tr(), child: Row( children: [ Icon(Icons.language_outlined, size: 20), @@ -28,23 +29,29 @@ class LanguageSwitcher extends StatelessWidget { ), ], ), - itemBuilder: (BuildContext context) => supportedLocales + initialValue: _supportedLocale, + onSelected: (value) { + EasyLocalization.of(context)?.setLocale(transformSupportedLocale(value)).then((_) { + var localeMap = Translations(CodegenLoader.mapLocales[context.locale.toString()]!); + if (!kIsWeb) WindowManager.instance.setTitle(localeMap.get(LocaleKeys.app_name)!); + var localSchemesProvider = context.read(); + var schemes = localSchemesProvider.schemes!; + var newSchemes = [ + schemes.first + ..scheme.name = localeMap.get(LocaleKeys.local_manager_default_scheme_label) + ..scheme.description = localeMap.get(LocaleKeys.local_manager_default_scheme_description), + ...schemes.skip(1), + ]; + localSchemesProvider.setProps(schemes: newSchemes); + }); + H().sp.setInt(SPKeys.userLanguage, value.index); + }, + itemBuilder: (BuildContext context) => SupportedLocale.values .map( - (locale) => PopupMenuItem( - value: SupportedLocale.zh_CN, - child: ListTile( - leading: Visibility( - child: Icon(CupertinoIcons.check_mark), - visible: _supportedLocale == locale, - ), - title: Text(supportedLocaleNames[SupportedLocale.values[supportedLocales.indexOf(locale)]] ?? ''), - ), - onTap: () { - EasyLocalization.of(context)?.setLocale(locale).then((_) { - if (!kIsWeb) WindowManager.instance.setTitle(LocaleKeys.app_name.tr()); - }); - H().sp.setInt(SPKeys.userLanguage, supportedLocales.indexOf(locale)); - }, + (supportedLocale) => CheckedPopupMenuItem( + value: supportedLocale, + checked: EasyLocalization.of(context)?.locale == transformSupportedLocale(supportedLocale), + child: Text(supportedLocaleNames[supportedLocale] ?? ''), ), ) .toList(), diff --git a/app/lib/widgets/table_cell_shortcut_listener.dart b/app/lib/widgets/table_cell_shortcut_listener.dart new file mode 100644 index 0000000..7d6e1fe --- /dev/null +++ b/app/lib/widgets/table_cell_shortcut_listener.dart @@ -0,0 +1,128 @@ +import 'package:dde_gesture_manager/constants/constants.dart'; +import 'package:dde_gesture_manager/extensions.dart'; +import 'package:dde_gesture_manager/models/settings.provider.dart'; +import 'package:dde_gesture_manager/utils/keyboard_mapper.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class TableCellShortcutListener extends StatefulWidget { + final double width; + final String initShortcut; + final Function(String shortcut) onComplete; + + const TableCellShortcutListener({ + Key? key, + this.width = 150.0, + required this.initShortcut, + required this.onComplete, + }) : super(key: key); + + @override + _TableCellShortcutListenerState createState() => _TableCellShortcutListenerState(); +} + +class _TableCellShortcutListenerState extends State { + List _shortcut = []; + bool inputMode = false; + FocusNode _focusNode = FocusNode(); + + _handleFocusChange() { + if (!_focusNode.hasFocus) { + widget.onComplete(_shortcut.join('+')); + } + } + + _tryToAddKey(PhysicalKeyboardKey key) { + var names = getPhysicalKeyNames(key.usbHidUsage); + if (names != null && !_shortcut.contains(names)) + setState(() { + _shortcut.add(names); + _shortcut.sort(); + }); + } + + @override + void initState() { + var __shortcut = widget.initShortcut.split('+'); + __shortcut.forEach((name) { + var keyNames = getPhysicalKeyNamesByRealName(name); + if (keyNames != null) _shortcut.add(keyNames); + }); + _shortcut.sort(); + _focusNode.addListener(_handleFocusChange); + super.initState(); + } + + @override + void dispose() { + _focusNode.removeListener(_handleFocusChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return RawKeyboardListener( + focusNode: _focusNode, + onKey: (evt) { + _tryToAddKey(evt.physicalKey); + }, + child: GestureDetector( + onTap: () { + setState(() { + _shortcut = []; + inputMode = true; + }); + }, + child: Focus( + autofocus: true, + onKeyEvent: (_, __) => KeyEventResult.skipRemainingHandlers, + child: Container( + height: kMinInteractiveDimension * .86, + width: widget.width, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(defaultBorderRadius), + color: Colors.grey.withOpacity(.3), + border: Border.all( + width: 2, + color: Focus.of(context).hasFocus + ? context.watch().activeColor ?? Color(0xff565656) + : Color(0xff565656)), + ), + child: Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: EdgeInsets.only(left: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: _shortcut + .map( + (e) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 2.0), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(defaultBorderRadius / 2), + color: context.t.dialogBackgroundColor, + border: Border.all(width: 1, color: Color(0xff565656)), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + child: Text( + e.displayName.notNull ? e.displayName : LocaleKeys.str_null.tr(), + style: TextStyle( + color: context.watch().activeColor, + ), + ), + ), + ), + ), + ) + .toList(), + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/app/lib/widgets/table_cell_text_field.dart b/app/lib/widgets/table_cell_text_field.dart new file mode 100644 index 0000000..3404173 --- /dev/null +++ b/app/lib/widgets/table_cell_text_field.dart @@ -0,0 +1,75 @@ +import 'package:dde_gesture_manager/constants/constants.dart'; +import 'package:dde_gesture_manager/models/settings.provider.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class TableCellTextField extends StatefulWidget { + final String? initText; + final String? hint; + final Function(String value) onComplete; + + const TableCellTextField({ + Key? key, + this.initText, + this.hint, + required this.onComplete, + }) : super(key: key); + + @override + _TableCellTextFieldState createState() => _TableCellTextFieldState(); +} + +class _TableCellTextFieldState extends State { + final FocusNode _focusNode = FocusNode( + onKeyEvent: (_, __) => KeyEventResult.skipRemainingHandlers, + ); + final TextEditingController _controller = TextEditingController(); + + @override + void initState() { + _focusNode.addListener(_handleFocusChange); + _controller.text = widget.initText ?? ''; + super.initState(); + } + + @override + void dispose() { + _focusNode.removeListener(_handleFocusChange); + super.dispose(); + } + + _handleFocusChange() { + if (!_focusNode.hasFocus) { + widget.onComplete(_controller.text); + } + } + + @override + Widget build(BuildContext context) { + return Container( + height: kMinInteractiveDimension * .86, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(defaultBorderRadius), + color: Colors.grey.withOpacity(.3), + border: Border.all( + width: 2, + color: Focus.of(context).hasFocus + ? context.watch().activeColor ?? + Color(0xff565656) + : Color(0xff565656)), + ), + child: Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: EdgeInsets.only(left: 15), + child: TextField( + focusNode: _focusNode, + cursorColor: context.watch().activeColor, + decoration: InputDecoration.collapsed(hintText: widget.hint), + controller: _controller, + ), + ), + ), + ); + } +} diff --git a/app/lib/widgets/theme_switcher.dart b/app/lib/widgets/theme_switcher.dart index a0f4df8..3ad2537 100644 --- a/app/lib/widgets/theme_switcher.dart +++ b/app/lib/widgets/theme_switcher.dart @@ -11,7 +11,7 @@ class ThemeSwitcher extends StatelessWidget { @override Widget build(BuildContext context) { var _brightnessMode = context.watch().brightnessMode; - return PopupMenuButton( + return PopupMenuButton( initialValue: _brightnessMode, child: Row( children: [ @@ -23,35 +23,23 @@ class ThemeSwitcher extends StatelessWidget { ], ), padding: EdgeInsets.zero, + onSelected: (value) => context.read().setProps(brightnessMode: value), tooltip: LocaleKeys.theme_tip.tr(), itemBuilder: (BuildContext context) => [ - PopupMenuItem( - child: ListTile( - leading: Visibility( - child: Icon(CupertinoIcons.check_mark), - visible: _brightnessMode == BrightnessMode.system, - ), - title: Text(LocaleKeys.theme_system).tr(), - ), - onTap: () => context.read().setProps(brightnessMode: BrightnessMode.system), + CheckedPopupMenuItem( + child: Text(LocaleKeys.theme_system).tr(), + checked: _brightnessMode == BrightnessMode.system, + value: BrightnessMode.system, ), - PopupMenuItem( - child: ListTile( - leading: Visibility( - child: Icon(CupertinoIcons.check_mark), - visible: _brightnessMode == BrightnessMode.light, - ), - title: Text(LocaleKeys.theme_light).tr()), - onTap: () => context.read().setProps(brightnessMode: BrightnessMode.light), + CheckedPopupMenuItem( + child: Text(LocaleKeys.theme_light).tr(), + checked: _brightnessMode == BrightnessMode.light, + value: BrightnessMode.light, ), - PopupMenuItem( - child: ListTile( - leading: Visibility( - child: Icon(CupertinoIcons.check_mark), - visible: _brightnessMode == BrightnessMode.dark, - ), - title: Text(LocaleKeys.theme_dark).tr()), - onTap: () => context.read().setProps(brightnessMode: BrightnessMode.dark), + CheckedPopupMenuItem( + child: Text(LocaleKeys.theme_dark).tr(), + checked: _brightnessMode == BrightnessMode.dark, + value: BrightnessMode.dark, ), ], ); diff --git a/app/pubspec.yaml b/app/pubspec.yaml index d76ae6c..b751a2c 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -35,6 +35,8 @@ dependencies: glass_kit: ^2.0.1 rect_getter: ^1.0.0 path_provider: ^2.0.5 + uuid: ^3.0.5 + adaptive_scrollbar: ^2.1.0 xdg_directories_web: path: 3rd_party/xdg_directories_web diff --git a/app/resources/langs/en.json b/app/resources/langs/en.json index f687fa5..9790aad 100644 --- a/app/resources/langs/en.json +++ b/app/resources/langs/en.json @@ -20,13 +20,17 @@ "tip": "Display help documentation" }, "market": { - "title": "Solution market" + "title": "Scheme market" }, "local_manager": { - "title": "Local solution management" + "title": "Local scheme management", + "default_scheme": { + "label": "System default scheme", + "description": "System default gesture scheme, cannot be modified. (Secretly telling you that there is no actual content in this scheme, just when applying the scheme, it is achieved by deleting a custom configuration scheme." + } }, "gesture_editor": { - "label": "Gesture program editing", + "label": "Gesture scheme editing", "gesture": "gesture", "direction": "direction", "fingers": "fingers", @@ -58,5 +62,24 @@ "delete": "delete", "duplicate": "duplicate", "apply": "apply" + }, + "str": { + "null": "Null" + }, + "built_in_commands": { + "ShowWorkspace": "ShowWorkspace", + "Handle4Or5FingersSwipeUp": "4/5FingersSwipeUp", + "Handle4Or5FingersSwipeDown": "4/5FingersSwipeDown", + "ToggleMaximize": "ToggleMaximize", + "Minimize": "Minimize", + "ShowWindow": "ShowWindow", + "ShowAllWindow": "ShowAllWindow", + "SwitchApplication": "SwitchApplication", + "ReverseSwitchApplication": "ReverseSwitchApplication", + "SwitchWorkspace": "SwitchWorkspace", + "ReverseSwitchWorkspace": "ReverseSwitchWorkspace", + "SplitWindowLeft": "SplitWindowLeft", + "SplitWindowRight": "SplitWindowRight", + "MoveWindow": "MoveWindow" } } \ No newline at end of file diff --git a/app/resources/langs/zh-CN.json b/app/resources/langs/zh-CN.json index ff97b4d..2883e08 100644 --- a/app/resources/langs/zh-CN.json +++ b/app/resources/langs/zh-CN.json @@ -23,7 +23,11 @@ "title": "方案市场" }, "local_manager": { - "title": "本地方案管理" + "title": "本地方案管理", + "default_scheme": { + "label": "系统默认方案", + "description": "系统默认的手势方案,不可修改。(偷偷告诉你,这个这个方案里没有任何实际内容,应用该方案时不过是通过删除自定义配置方案的方式来实现恢复系统默认方案~)" + } }, "gesture_editor": { "label": "手势方案编辑", @@ -58,5 +62,24 @@ "delete": "删除", "duplicate": "复制", "apply": "应用" + }, + "str": { + "null": "无" + }, + "built_in_commands": { + "ShowWorkspace": "显示工作区", + "Handle4Or5FingersSwipeUp": "4/5指向上划", + "Handle4Or5FingersSwipeDown": "4/5指向下划", + "ToggleMaximize": "全屏切换", + "Minimize": "最小化", + "ShowWindow": "显示桌面", + "ShowAllWindow": "查看所有窗口", + "SwitchApplication": "切换应用窗口", + "ReverseSwitchApplication": "反向切换应用窗口", + "SwitchWorkspace": "切换工作区", + "ReverseSwitchWorkspace": "反向切换工作区", + "SplitWindowLeft": "向左分屏", + "SplitWindowRight": "向右分屏", + "MoveWindow": "移动窗口" } } \ No newline at end of file