JJ_MKVMaker/lib/ui/explorer_drag.dart

100 lines
3.8 KiB
Dart

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<String> 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<T extends Object> extends Draggable<T> {
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<String> 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: [
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: TextStyle(fontSize: 12, color: JjColors.text)),
]),
),
);
/// 82: 놓았을 때 묻기 - 복사 (Enter, 처음 고름) · 이동 · 취소. 취소하면 null
Future<bool?> askDropAction(BuildContext context, List<String> paths, String dest) => showDialog<bool>(
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('복사'))),
],
),
);