131 lines
7.0 KiB
Dart
131 lines
7.0 KiB
Dart
import 'dart:math' as math;
|
||
|
||
import 'package:flutter/material.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 '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;
|
||
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),
|
||
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);
|
||
});
|
||
}
|
||
}
|