From c6d2a566a4622acf9fd0386f1f4f3769ea7b037c Mon Sep 17 00:00:00 2001 From: ArthurHeitmann <37270165+ArthurHeitmann@users.noreply.github.com> Date: Thu, 5 Sep 2024 19:59:51 +0200 Subject: [PATCH] drag & drop files onto supported text fields --- .../openFiles/types/SmdFileData.dart | 3 +- .../openFiles/types/TmdFileData.dart | 3 +- lib/widgets/filesView/fileTabView.dart | 2 +- .../filesView/types/SaveSlotDataEditor.dart | 6 +- .../types/genericTable/tableEditor.dart | 12 +--- lib/widgets/filesView/types/wtaWtpEditor.dart | 3 +- lib/widgets/layout/searchPanel.dart | 6 +- lib/widgets/misc/dropTargetBuilder.dart | 18 +++++- lib/widgets/misc/imagePreviewBuilder.dart | 5 +- lib/widgets/misc/preferencesEditor.dart | 8 +-- .../propEditors/UnderlinePropTextField.dart | 2 +- .../propEditors/primaryPropTextField.dart | 2 +- lib/widgets/propEditors/propTextField.dart | 61 +++++++++++++++++++ .../propEditors/transparentPropTextField.dart | 2 +- 14 files changed, 104 insertions(+), 29 deletions(-) diff --git a/lib/stateManagement/openFiles/types/SmdFileData.dart b/lib/stateManagement/openFiles/types/SmdFileData.dart index 7082276b..29b9e9d5 100644 --- a/lib/stateManagement/openFiles/types/SmdFileData.dart +++ b/lib/stateManagement/openFiles/types/SmdFileData.dart @@ -7,6 +7,7 @@ import '../../../fileTypeUtils/smd/smdReader.dart'; import '../../../fileTypeUtils/smd/smdWriter.dart'; import '../../../widgets/filesView/FileType.dart'; import '../../../widgets/filesView/types/genericTable/tableEditor.dart'; +import '../../../widgets/propEditors/propTextField.dart'; import '../../Property.dart'; import '../../changesExporter.dart'; import '../../hasUuid.dart'; @@ -165,7 +166,7 @@ class SmdData extends ListNotifier<SmdEntryData> with CustomTableConfig, Undoabl key: Key(entry.uuid), cells: [ PropCellConfig(prop: entry.id), - PropCellConfig(prop: entry.text, allowMultiline: true), + PropCellConfig(prop: entry.text, options: const PropTFOptions(isMultiline: true)), ], ); } diff --git a/lib/stateManagement/openFiles/types/TmdFileData.dart b/lib/stateManagement/openFiles/types/TmdFileData.dart index f8f87865..bd0b70ab 100644 --- a/lib/stateManagement/openFiles/types/TmdFileData.dart +++ b/lib/stateManagement/openFiles/types/TmdFileData.dart @@ -7,6 +7,7 @@ import '../../../fileTypeUtils/tmd/tmdReader.dart'; import '../../../fileTypeUtils/tmd/tmdWriter.dart'; import '../../../widgets/filesView/FileType.dart'; import '../../../widgets/filesView/types/genericTable/tableEditor.dart'; +import '../../../widgets/propEditors/propTextField.dart'; import '../../Property.dart'; import '../../changesExporter.dart'; import '../../hasUuid.dart'; @@ -165,7 +166,7 @@ class TmdData extends ListNotifier<TmdEntryData> with CustomTableConfig, Undoabl key: Key(entry.uuid), cells: [ PropCellConfig(prop: entry.id), - PropCellConfig(prop: entry.text, allowMultiline: true), + PropCellConfig(prop: entry.text, options: const PropTFOptions(isMultiline: true)), ], ); } diff --git a/lib/widgets/filesView/fileTabView.dart b/lib/widgets/filesView/fileTabView.dart index 05065890..4a8a503c 100644 --- a/lib/widgets/filesView/fileTabView.dart +++ b/lib/widgets/filesView/fileTabView.dart @@ -131,9 +131,9 @@ class _FileTabViewState extends ChangeNotifierState<FileTabView> { children: [ for (var (i, file) in widget.viewArea.files.indexed) IndexedStackIsVisible( + key: Key(file.uuid), isVisible: i == currentFileIndex, child: ConstrainedBox( - key: Key(file.uuid), constraints: const BoxConstraints.expand(), child: FocusTraversalGroup( child: makeFileEditor(file), diff --git a/lib/widgets/filesView/types/SaveSlotDataEditor.dart b/lib/widgets/filesView/types/SaveSlotDataEditor.dart index 4864f328..c3de265c 100644 --- a/lib/widgets/filesView/types/SaveSlotDataEditor.dart +++ b/lib/widgets/filesView/types/SaveSlotDataEditor.dart @@ -200,7 +200,7 @@ class _InventoryTableConfig with CustomTableConfig { key: Key(items[i].uuid), cells: [ TextCellConfig(i.toString()), - PropCellConfig(prop: items[i].id, autocompleteOptions: _getItemIdAutocomplete), + PropCellConfig(prop: items[i].id, options: const PropTFOptions(autocompleteOptions: _getItemIdAutocomplete)), PropCellConfig(prop: items[i].count), PropCellConfig(prop: items[i].isActive), ] @@ -279,7 +279,7 @@ class _WeaponTableConfig with CustomTableConfig { key: Key(weapons[i].uuid), cells: [ TextCellConfig(weapons[i].index.toString().padLeft(2, " ") + (i < _weaponsByIndex.length ? " (${_weaponsByIndex[i].item2})" : "")), - PropCellConfig(prop: weapons[i].id, autocompleteOptions: _getWeaponIdAutocomplete), + PropCellConfig(prop: weapons[i].id, options: const PropTFOptions(autocompleteOptions: _getWeaponIdAutocomplete)), PropCellConfig(prop: weapons[i].level), PropCellConfig(prop: weapons[i].isNew), PropCellConfig(prop: weapons[i].hasNewStory), @@ -394,7 +394,7 @@ class _StringListTableConfig with CustomTableConfig { cells: [ PropCellConfig( prop: strings[index].text, - autocompleteOptions: autocompleteOptions, + options: PropTFOptions(autocompleteOptions: autocompleteOptions), ), CustomWidgetCellConfig( IconButton( diff --git a/lib/widgets/filesView/types/genericTable/tableEditor.dart b/lib/widgets/filesView/types/genericTable/tableEditor.dart index 06b7f601..df2a9a08 100644 --- a/lib/widgets/filesView/types/genericTable/tableEditor.dart +++ b/lib/widgets/filesView/types/genericTable/tableEditor.dart @@ -1,6 +1,4 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import '../../../../stateManagement/Property.dart'; @@ -11,7 +9,6 @@ import '../../../misc/nestedContextMenu.dart'; import '../../../propEditors/UnderlinePropTextField.dart'; import '../../../propEditors/propEditorFactory.dart'; import '../../../propEditors/propTextField.dart'; -import '../../../propEditors/textFieldAutocomplete.dart'; import '../../../propEditors/transparentPropTextField.dart'; import '../../../theme/customTheme.dart'; import 'tableExporter.dart'; @@ -23,19 +20,16 @@ abstract class CellConfig { class PropCellConfig extends CellConfig { final Prop prop; - final bool allowMultiline; - final FutureOr<Iterable<AutocompleteConfig>> Function()? autocompleteOptions; + final PropTFOptions options; - PropCellConfig({ required this.prop, this.allowMultiline = false, this.autocompleteOptions }); + PropCellConfig({ required this.prop, this.options = const PropTFOptions() }); @override Widget makeWidget() => makePropEditor<TransparentPropTextField>( prop, - PropTFOptions( + options.copyWith( constraints: const BoxConstraints(minWidth: double.infinity, minHeight: 30), - isMultiline: allowMultiline, useIntrinsicWidth: false, - autocompleteOptions: autocompleteOptions, ), ); diff --git a/lib/widgets/filesView/types/wtaWtpEditor.dart b/lib/widgets/filesView/types/wtaWtpEditor.dart index 2261d451..95df309b 100644 --- a/lib/widgets/filesView/types/wtaWtpEditor.dart +++ b/lib/widgets/filesView/types/wtaWtpEditor.dart @@ -9,6 +9,7 @@ import '../../../stateManagement/Property.dart'; import '../../../stateManagement/listNotifier.dart'; import '../../../stateManagement/openFiles/types/WtaWtpData.dart'; import '../../../utils/utils.dart'; +import '../../propEditors/propTextField.dart'; import '../../theme/customTheme.dart'; import 'genericTable/tableEditor.dart'; @@ -53,7 +54,7 @@ class _TexturesTableConfig with CustomTableConfig { key: Key(textures[index].uuid), cells: [ PropCellConfig(prop: textures[index].id), - PropCellConfig(prop: textures[index].path), + PropCellConfig(prop: textures[index].path, options: const PropTFOptions(isFilePath: true)), CustomWidgetCellConfig(IconButton( icon: const Icon(Icons.folder, size: 20), onPressed: () => _selectTexture(index), diff --git a/lib/widgets/layout/searchPanel.dart b/lib/widgets/layout/searchPanel.dart index 6c2d8abc..336d7c81 100644 --- a/lib/widgets/layout/searchPanel.dart +++ b/lib/widgets/layout/searchPanel.dart @@ -315,7 +315,7 @@ class _SearchPanelState extends State<SearchPanel> { const SizedBox() ], ), - makePropEditor(path, const PropTFOptions(hintText: "Path", useIntrinsicWidth: false)), + makePropEditor(path, const PropTFOptions(hintText: "Path", useIntrinsicWidth: false, isFolderPath: true)), makePropEditor(extensions, const PropTFOptions(hintText: "Extensions (.xml, .rb, ...)", useIntrinsicWidth: false)), ].map((e) => Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 2.0), @@ -346,7 +346,7 @@ class _SearchPanelState extends State<SearchPanel> { ], ), if (!useIndexedData.value) ...[ - makePropEditor(path, const PropTFOptions(hintText: "Path", useIntrinsicWidth: false)), + makePropEditor(path, const PropTFOptions(hintText: "Path", useIntrinsicWidth: false, isFolderPath: true)), ] ].map((e) => Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 2.0), @@ -360,7 +360,7 @@ class _SearchPanelState extends State<SearchPanel> { Widget _makeEstSearchOptions() { return Column( children: [ - makePropEditor(path, const PropTFOptions(hintText: "Path", useIntrinsicWidth: false)), + makePropEditor(path, const PropTFOptions(hintText: "Path", useIntrinsicWidth: false, isFolderPath: true)), ...estOptionsNamed.map((opt) => RowSeparated( crossAxisAlignment: CrossAxisAlignment.center, separatorWidth: 5, diff --git a/lib/widgets/misc/dropTargetBuilder.dart b/lib/widgets/misc/dropTargetBuilder.dart index 13ea35d4..0313a4c4 100644 --- a/lib/widgets/misc/dropTargetBuilder.dart +++ b/lib/widgets/misc/dropTargetBuilder.dart @@ -1,4 +1,6 @@ +import 'dart:math'; + import 'package:desktop_drop/desktop_drop.dart'; import 'package:flutter/material.dart'; @@ -15,15 +17,27 @@ class DropTargetBuilder extends StatefulWidget { } class _DropTargetBuilderState extends State<DropTargetBuilder> { + static int _dropping = 0; + bool isDropping = false; @override Widget build(BuildContext context) { return DropTarget( enable: ModalRoute.of(context)!.isCurrent && IndexedStackIsVisible.of(context) != false, - onDragEntered: (_) => setState(() => isDropping = true), - onDragExited: (_) => setState(() => isDropping = false), + onDragEntered: (_) { + _dropping += 1; + isDropping = true; + setState(() {}); + }, + onDragExited: (_) { + _dropping = max(0, _dropping - 1); + isDropping = false; + setState(() {}); + }, onDragDone: (details) { + if (_dropping > 0) + return; widget.onDrop(details.files.map((f) => f.path).toList()); }, child: widget.builder(context, isDropping), diff --git a/lib/widgets/misc/imagePreviewBuilder.dart b/lib/widgets/misc/imagePreviewBuilder.dart index c4ddc5ec..cd7b9691 100644 --- a/lib/widgets/misc/imagePreviewBuilder.dart +++ b/lib/widgets/misc/imagePreviewBuilder.dart @@ -5,6 +5,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import '../../fileTypeUtils/textures/ddsConverter.dart'; +import '../../utils/utils.dart'; enum ImagePreviewState { loading, @@ -57,7 +58,9 @@ class _ImagePreviewBuilderState extends State<ImagePreviewBuilder> { exists = true; var sw = Stopwatch()..start(); image = texToPng(widget.path, maxHeight: widget.maxHeight, verbose: false) - ..then((_) => print("Loaded image in ${sw.elapsedMilliseconds}ms")); + ..then((_) { + debugOnly(() => print("Loaded image in ${sw.elapsedMilliseconds}ms")); + }); setState(() {}); } diff --git a/lib/widgets/misc/preferencesEditor.dart b/lib/widgets/misc/preferencesEditor.dart index ea7f4ddc..5b6b0edf 100644 --- a/lib/widgets/misc/preferencesEditor.dart +++ b/lib/widgets/misc/preferencesEditor.dart @@ -90,7 +90,7 @@ class _PreferencesEditorState extends ChangeNotifierState<PreferencesEditor> { prop: exportPathProp, onValid: (value) => exportPathProp.value = value, validatorOnChange: (value) => value.isEmpty || Directory(value).existsSync() ? null : "Directory does not exist", - options: const PropTFOptions(constraints: BoxConstraints(minHeight: 30)), + options: const PropTFOptions(constraints: BoxConstraints(minHeight: 30), isFolderPath: true), ), ), makeFilePickerButton( @@ -162,7 +162,7 @@ class _PreferencesEditorState extends ChangeNotifierState<PreferencesEditor> { prop: path, onValid: (value) => widget.prefs.indexingPaths!.setPath(path, value), validatorOnChange: (value) => Directory(value).existsSync() ? null : "Directory does not exist", - options: const PropTFOptions(constraints: BoxConstraints(minHeight: 30)), + options: const PropTFOptions(constraints: BoxConstraints(minHeight: 30), isFolderPath: true), ), ), makeFilePickerButton( @@ -263,7 +263,7 @@ class _PreferencesEditorState extends ChangeNotifierState<PreferencesEditor> { prop: widget.prefs.waiExtractDir!, onValid: (value) => widget.prefs.waiExtractDir!.value = value, validatorOnChange: (value) => value.isEmpty || Directory(value).existsSync() ? null : "Directory does not exist", - options: const PropTFOptions(constraints: BoxConstraints(minHeight: 30)), + options: const PropTFOptions(constraints: BoxConstraints(minHeight: 30), isFolderPath: true), ), ), makeFilePickerButton( @@ -282,7 +282,7 @@ class _PreferencesEditorState extends ChangeNotifierState<PreferencesEditor> { prop: widget.prefs.wwiseCliPath!, onValid: (value) => widget.prefs.wwiseCliPath!.value = value.isNotEmpty ? findWwiseCliExe(value)! : "", validatorOnChange: (value) => value.isEmpty || findWwiseCliExe(value) != null ? null : "Directory does not exist", - options: const PropTFOptions(constraints: BoxConstraints(minHeight: 30)), + options: const PropTFOptions(constraints: BoxConstraints(minHeight: 30), isFolderPath: true, isFilePath: true), ), ), makeFilePickerButton( diff --git a/lib/widgets/propEditors/UnderlinePropTextField.dart b/lib/widgets/propEditors/UnderlinePropTextField.dart index a8bae3bb..a90b4c03 100644 --- a/lib/widgets/propEditors/UnderlinePropTextField.dart +++ b/lib/widgets/propEditors/UnderlinePropTextField.dart @@ -33,7 +33,7 @@ class _UnderlinePropTextFieldState extends PropTextFieldState { decoration: BoxDecoration( border: Border(bottom: BorderSide(color: getTheme(context).propBorderColor!, width: 2)), ), - child: intrinsicWidthWrapper( + child: applyWrappers( child: ConstrainedBox( constraints: widget.options.constraints, child: Row( diff --git a/lib/widgets/propEditors/primaryPropTextField.dart b/lib/widgets/propEditors/primaryPropTextField.dart index bf2f9150..68d75bab 100644 --- a/lib/widgets/propEditors/primaryPropTextField.dart +++ b/lib/widgets/propEditors/primaryPropTextField.dart @@ -33,7 +33,7 @@ class _PrimaryPropTextFieldState extends PropTextFieldState { child: Material( color: getTheme(context).formElementBgColor, borderRadius: BorderRadius.circular(8.0), - child: intrinsicWidthWrapper( + child: applyWrappers( child: ConstrainedBox( constraints: widget.options.constraints, child: Row( diff --git a/lib/widgets/propEditors/propTextField.dart b/lib/widgets/propEditors/propTextField.dart index 4f793cdb..761e8720 100644 --- a/lib/widgets/propEditors/propTextField.dart +++ b/lib/widgets/propEditors/propTextField.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; @@ -7,6 +8,8 @@ import '../../stateManagement/Property.dart'; import '../../utils/utils.dart'; import '../misc/ChangeNotifierWidget.dart'; import '../misc/TextFieldFocusNode.dart'; +import '../misc/dropTargetBuilder.dart'; +import '../theme/customTheme.dart'; import 'DoubleClickablePropTextField.dart'; import 'UnderlinePropTextField.dart'; import 'primaryPropTextField.dart'; @@ -18,6 +21,8 @@ class PropTFOptions { final BoxConstraints constraints; final bool isMultiline; final bool useIntrinsicWidth; + final bool isFolderPath; + final bool isFilePath; final String? hintText; final FutureOr<Iterable<AutocompleteConfig>> Function()? autocompleteOptions; @@ -26,6 +31,8 @@ class PropTFOptions { this.constraints = const BoxConstraints(minWidth: 50), this.isMultiline = false, this.useIntrinsicWidth = true, + this.isFolderPath = false, + this.isFilePath = false, this.hintText, this.autocompleteOptions, }); @@ -35,6 +42,8 @@ class PropTFOptions { BoxConstraints? constraints, bool? isMultiline, bool? useIntrinsicWidth, + bool? isFolderPath, + bool? isFilePath, String? hintText, FutureOr<Iterable<AutocompleteConfig>> Function()? autocompleteOptions, }) { @@ -43,6 +52,8 @@ class PropTFOptions { constraints: constraints ?? this.constraints, isMultiline: isMultiline ?? this.isMultiline, useIntrinsicWidth: useIntrinsicWidth ?? this.useIntrinsicWidth, + isFolderPath: isFolderPath ?? this.isFolderPath, + isFilePath: isFilePath ?? this.isFilePath, hintText: hintText ?? this.hintText, autocompleteOptions: autocompleteOptions ?? this.autocompleteOptions, ); @@ -178,7 +189,57 @@ abstract class PropTextFieldState<P extends Prop> extends ChangeNotifierState<Pr onValid(text); } + Widget applyWrappers({ required Widget child }) { + return pathDropTargetWrapper( + child: intrinsicWidthWrapper( + child: child, + ), + ); + } + Widget intrinsicWidthWrapper({ required Widget child }) => widget.options.useIntrinsicWidth ? IntrinsicWidth(child: child) : child; + + Widget pathDropTargetWrapper({ required Widget child }) => widget.options.isFolderPath || widget.options.isFilePath + ? DropTargetBuilder( + onDrop: (files) async { + var file = files.first; + if (!widget.options.isFolderPath && await Directory(file).exists()) { + showToast("Expected a file, not a folder"); + return; + } + if (!widget.options.isFilePath && await File(file).exists()) { + showToast("Expected a folder, not a file"); + return; + } + + controller.text = file; + onValid(file); + }, + builder: (context, isDropping) => Stack( + children: [ + child, + if (isDropping) + Positioned.fill( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: DecoratedBox( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: getTheme(context).editorBackgroundColor!.withOpacity(0.75), + blurRadius: 10, + spreadRadius: 5, + ), + ], + ), + ), + ), + ), + ], + ), + ) + : child; + } diff --git a/lib/widgets/propEditors/transparentPropTextField.dart b/lib/widgets/propEditors/transparentPropTextField.dart index 4c0822e7..48f39248 100644 --- a/lib/widgets/propEditors/transparentPropTextField.dart +++ b/lib/widgets/propEditors/transparentPropTextField.dart @@ -30,7 +30,7 @@ class _TransparentPropTextFieldState extends PropTextFieldState { notifier: widget.prop, builder: (context) => Padding( padding: const EdgeInsets.symmetric(vertical: 3), - child: intrinsicWidthWrapper( + child: applyWrappers( child: ConstrainedBox( constraints: widget.options.constraints, child: Row(