100 lines
3.8 KiB
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('복사'))),
|
|
],
|
|
),
|
|
);
|