import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import '../core/file_ops.dart'; import '../core/vfs.dart'; import '../l10n/tr.dart'; import 'theme.dart'; /// 82: 창 사이 끌어 놓기로 옮기는 것 - 어느 창 ([from], 창 번호) 의 어떤 항목들 class DragLoad { final int from; final List paths; const DragLoad(this.from, this.paths); } /// 82: [load] 를 [to] 창의 [dest] 폴더에 놓을 수 있는지 - 같은 창 · 자기 안 · 원본 폴더 자신은 안 됨 bool canDropInto(DragLoad load, int to, String dest) { if (load.from == to || load.paths.isEmpty) return false; if (load.paths.any((x) => isSameOrInside(dest, x))) return false; if (load.paths.every((x) => samePath(vDirname(x), dest))) return false; return true; } /// 82: 마우스로 [slop] (8px) 넘게 움직였을 때만 끌기 - 한 번 누르기 (고르기) · 두 번 누르기 (열기) 가 흔들리지 않게 class MouseDraggable extends Draggable { final double slop; const MouseDraggable({ super.key, required super.child, required super.feedback, super.data, super.childWhenDragging, super.onDragStarted, super.onDragEnd, this.slop = 8, }); @override MultiDragGestureRecognizer createRecognizer(GestureMultiDragStartCallback onStart) => _SlopMultiDrag(slop, supportedDevices: const {PointerDeviceKind.mouse})..onStart = onStart; } class _SlopMultiDrag extends MultiDragGestureRecognizer { final double slop; _SlopMultiDrag(this.slop, {super.supportedDevices}) : super(debugOwner: null); @override MultiDragPointerState createNewPointerState(PointerDownEvent event) => _SlopState(slop, event.position, event.kind, gestureSettings); @override String get debugDescription => 'explorer drag'; } class _SlopState extends MultiDragPointerState { final double slop; _SlopState(this.slop, super.initialPosition, super.kind, super.gestureSettings); @override void checkForResolutionAfterMove() { if (pendingDelta!.distance > slop) resolve(GestureDisposition.accepted); } @override void accepted(GestureMultiDragStartCallback starter) => starter(initialPosition); } /// 82: 끄는 동안 손가락 · 마우스 옆에 보이는 것: "n개 · 첫 이름" Widget dragFeedback(List paths) => Material( color: Colors.transparent, child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: JjColors.panelHigh, borderRadius: BorderRadius.circular(6), border: Border.all(color: JjColors.accent), ), child: Row(mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.drive_file_move_outline, size: 16, color: JjColors.accent), const SizedBox(width: 6), Text(paths.length == 1 ? vBasename(paths.first) : trf('{0}개 · {1} …', [paths.length, vBasename(paths.first)]), style: const TextStyle(fontSize: 12, color: JjColors.text)), ]), ), ); /// 82: 놓았을 때 묻기 - 복사 (Enter, 처음 고름) · 이동 · 취소. 취소하면 null Future askDropAction(BuildContext context, List paths, String dest) => showDialog( context: context, builder: (ctx) => AlertDialog( scrollable: true, title: Text(trf('{0}개 항목을 놓았습니다', [paths.length])), content: Text('${vDisplay(dest)}\n${paths.take(5).map(vBasename).join(', ')}${paths.length > 5 ? ' …' : ''}'), actions: [ TextButton(onPressed: () => Navigator.pop(ctx), child: Text(tr('취소'))), OutlinedButton(onPressed: () => Navigator.pop(ctx, true), child: Text(tr('이동'))), FilledButton(autofocus: true, onPressed: () => Navigator.pop(ctx, false), child: Text(tr('복사'))), ], ), );