JJ_MKVMaker/lib/ui/explorer/explorer_body.dart

162 lines
8.1 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import 'dart:math' as math;
import 'package:flutter/material.dart';
import '../../app/settings.dart' show AppSettings;
import '../../core/file_ops.dart' show samePath;
import '../explorer_look.dart';
import 'explorer_bars.dart';
import 'explorer_buttons.dart';
import 'explorer_controller.dart';
import 'explorer_flows.dart';
import 'explorer_layout.dart';
import 'explorer_select.dart';
import 'pane_view.dart';
import 'tc_key_bar.dart';
import 'toolbar.dart';
/// 약속 (E5d-6): 창들과 기능 버튼 줄의 배치 - 설정 (두 창 · 폴더 + 파일 목록 · 한 창, 좌우 · 위아래, 버튼 줄 사이 · 끝 · 숨김) 과
/// 폭 (좁으면 잠깐 한 창 · 좁은 트리 · 좁은 Rsync 는 창 하나씩) 을 [ExplorerLayout] 에 적고 그린다. 창은 배치가 바뀌어도 같은 위젯
/// (스크롤이 맨 위로 가지 않게). 모양이 바뀌면 보던 폴더가 다시 보이게 한다
class ExplorerPanesBody extends StatelessWidget {
const ExplorerPanesBody(
{super.key, required this.ctl, required this.sel, required this.layout, required this.parts, required this.buttons, required this.flows});
final ExplorerController ctl;
final ExplorerSelect sel;
final ExplorerLayout layout;
final ExplorerPaneParts parts;
final ExplorerButtons buttons;
final ExplorerFlows flows;
@override
Widget build(BuildContext context) {
final s = ctl.c.settings;
final shape = '${s.explorer.style}|${s.explorer.layout}|${s.explorer.orientation}|${s.explorer.toolbar}';
if (ctl.ready && shape != layout.shape) {
if (layout.shape.isNotEmpty) {
for (final pane in ctl.panes) {
layout.reveal(pane, pane.current);
}
}
layout.shape = shape;
}
final dual = layout.dual;
final panes = _panes(context, s, dual);
// E6: Total Commander 모양은 아래에 F3~F8 단추 줄 (키 F3~F8 과 같은 일)
if (ctl.look != ExplorerStyle.totalcmd || ctl.rsync) return panes;
return Column(children: [Expanded(child: panes), TcKeyBar(onPressed: (k) => _tcKey(context, k))]);
}
void _tcKey(BuildContext context, TcKey k) {
final pane = ctl.pane;
switch (k) {
case TcKey.view:
flows.opener.quickLook(context, pane);
case TcKey.edit:
final f = pane.focused;
if (f != null && !samePath(f, pane.root)) flows.opener.open(context, f, pane: pane);
case TcKey.copy:
buttons.press(ExplorerButton.copy);
case TcKey.move:
buttons.press(ExplorerButton.move);
case TcKey.newFolder:
buttons.press(ExplorerButton.newFolder);
case TcKey.delete:
buttons.press(ExplorerButton.delete);
}
}
Widget _panes(BuildContext context, AppSettings s, bool dual) {
return LayoutBuilder(builder: (context, box) {
// 두 창이 다 들어가는지: 트리 (화살표 · 아이콘 · 여백 78 + 이름 40) + 목록 (아이콘 · ✔ · 여백 104 + 이름 50), 이름은 글자 배율만큼.
// 1배면 360 폭에도 들어가고, 기기 글자 1.3 × 150% (약 2배) 면 412 폭에도 모자란다
final wantSplit = !ctl.rsync && s.explorer.layout == 'split';
final textScale = MediaQuery.textScalerOf(context).scale(14) / 14 * layout.listTextScale;
final barW = s.explorer.toolbar == 'hidden' ? 0.0 : 72.0;
final sideNow = switch (s.explorer.orientation) { 'side' => true, 'stacked' => false, _ => box.maxWidth >= box.maxHeight };
final tightWas = layout.splitTight;
layout.splitTight = wantSplit && sideNow && box.maxWidth - barW < 182 + 90 * textScale;
// 바뀌면 이 builder 밖 (버튼 이름 "두 창" 등) 도 한 번 다시 그린다
if (tightWas != layout.splitTight) {
ctl.host.afterFrame(() {
if (ctl.host.alive) ctl.update();
});
}
final split = layout.split;
// 폭이 바뀌면 (좁은 한 창 ↔ 두 창) 이 builder 만 다시 불린다 - 그때도 새로 붙은 창을 보이게
if (ctl.ready) layout.revealNewViews();
final side = !(dual || split) ||
switch (s.explorer.orientation) {
'side' => true,
'stacked' => false,
_ => box.maxWidth >= box.maxHeight,
};
// 191: Rsync 화면이 좁으면 (폴드 접은 화면 · 폰 세로의 좌우 두 창) 창 하나씩 - [왼쪽 | 오른쪽] 으로 바꿔 본다
// (두 창 + 가운데 버튼 줄이면 창이 140px 남짓이라 폴더 이름이 잘렸다)
final rsyncOne = ctl.rsync && side && box.maxWidth < rsyncTwoPaneMinWidth;
layout.narrowSplit = split && side && box.maxWidth < ExplorerLayout.narrowSplitWidth;
final bar = s.explorer.toolbar == 'hidden' && !ctl.rsync
? null
: ExplorerToolbar(
// X-plore b: 길게 누르기 = 짝 동작 (Rsync 화면은 없음)
onLongPress: ctl.rsync ? null : buttons.longPress,
// Rsync 화면: 늘 같은 버튼 (→ ← ⇄ 모니터링) 을 두 창 사이에
buttons: ctl.rsync ? ExplorerButton.rsyncButtons : ExplorerButton.fromSettings(s.explorer.buttons, android: ctl.c.services.platform.isAndroid),
vertical: side && !rsyncOne,
panesSide: side,
onPressed: buttons.press,
enabled: (b) => switch (b) {
ExplorerButton.paste => flows.transfers.clip.isNotEmpty,
_ => true,
},
selected: (b) =>
(b == ExplorerButton.hidden && s.explorer.showHidden) || (b == ExplorerButton.select && sel.selecting),
twoPanes: dual || split,
pinned: {ExplorerButton.orient, if (flows.transfers.clip.isNotEmpty) ExplorerButton.paste},
);
final middle = bar != null && (ctl.rsync || s.explorer.toolbar == 'middle');
final edge = bar != null && !ctl.rsync && s.explorer.toolbar == 'edge';
// 180: 창마다 같은 열쇠 - 가로 ↔ 세로 배치가 바뀌어도 (창 크기 · 작업 현황이 열림) 창을 새로 만들지 않는다 (스크롤이 맨 위로 가지 않게)
Widget keep(int k, Widget w) => KeyedSubtree(key: layout.viewKeys[k], child: w);
if (rsyncOne) {
final i = ctl.active.clamp(0, 1);
return Column(children: [
RsyncPaneSwitch(ctl: ctl),
?bar,
Expanded(child: keep(i, ExplorerPaneView(ctl: ctl, sel: sel, parts: parts, index: i, foldersOnly: true))),
]);
}
final children = <Widget>[
if (dual) ...[
Expanded(child: keep(0, ExplorerPaneView(ctl: ctl, sel: sel, parts: parts, index: 0, foldersOnly: ctl.rsync))),
if (middle) bar,
Expanded(child: keep(1, ExplorerPaneView(ctl: ctl, sel: sel, parts: parts, index: 1, foldersOnly: ctl.rsync))),
if (edge) bar,
] else if (split) ...[
// 왼쪽 폴더 트리 (좁게) · 오른쪽 그 폴더의 파일 목록. 좁은 화면은 트리에 최소 폭 (130px 또는 버튼 줄 (72) 을 뺀 폭의 42%) -
// 버튼 줄까지 넣어 38% 로 하면 360 폭에서 오른쪽 목록의 파일 이름이 두세 글자만 남았다
if (layout.narrowSplit)
SizedBox(
width: math.max(130.0, (box.maxWidth - (middle || edge ? 72 : 0)) * 0.42),
child: keep(0, ExplorerPaneView(ctl: ctl, sel: sel, parts: parts, index: 0, foldersOnly: true)))
else
Expanded(flex: 2, child: keep(0, ExplorerPaneView(ctl: ctl, sel: sel, parts: parts, index: 0, foldersOnly: true))),
if (middle) bar,
Expanded(flex: 3, child: keep(2, ExplorerListView(ctl: ctl, sel: sel, parts: parts, pane: ctl.panes[0]))),
if (edge) bar,
] else ...[
if (middle) bar,
Expanded(
child: layout.hiddenTarget
? Column(children: [ListenableBuilder(listenable: ctl.panes[1], builder: (_, _) => CopyTargetBar(ctl: ctl)), Expanded(child: keep(0, ExplorerPaneView(ctl: ctl, sel: sel, parts: parts, index: 0)))])
: keep(0, ExplorerPaneView(ctl: ctl, sel: sel, parts: parts, index: 0))),
if (edge) bar,
],
];
return side ? Row(children: children) : Column(children: children);
});
}
}