109 lines
6.2 KiB
Dart
109 lines
6.2 KiB
Dart
import 'package:flutter/material.dart';
|
||
|
||
import '../../core/file_ops.dart' show samePath;
|
||
import '../../l10n/tr.dart';
|
||
import '../explorer_look.dart';
|
||
import '../app_actions.dart' show isCompact;
|
||
import 'explorer_controller.dart';
|
||
import 'explorer_pane.dart';
|
||
|
||
/// 191: Rsync 화면이 좌우 두 창 + 가운데 버튼 줄로 보이는 가장 작은 폭 (이보다 좁으면 창 하나씩)
|
||
const double rsyncTwoPaneMinWidth = 560;
|
||
|
||
/// 약속 (E5d-6): 창 배치 규칙 - 두 창 · 폴더 + 파일 목록 · 한 창 (설정, "자동" 은 화면 크기 따라), 좁아서 잠깐 한 창 ([splitTight]) ·
|
||
/// 좁은 "폴더 + 파일 목록" ([narrowSplit]) 은 [ExplorerPanesBody] 가 폭을 보고 정한다. 줄 높이 (목록 글자 크기 · 줄 간격),
|
||
/// 복사 · 이동 갈 곳 창 ([other]), 밀어서 창 바꾸기, 항목이 보이게 스크롤 ([reveal]). [context]: 화면 (글자 크기 · 폭)
|
||
class ExplorerLayout {
|
||
ExplorerLayout(this.ctl, {required this.context});
|
||
|
||
final ExplorerController ctl;
|
||
final BuildContext Function() context;
|
||
|
||
/// 180: 왼쪽 창 · 오른쪽 창 · 파일 목록 (배치가 바뀌어도 같은 위젯)
|
||
final viewKeys = [GlobalKey(), GlobalKey(), GlobalKey()];
|
||
|
||
/// 모양 · 배치가 바뀌면 (줄 높이 · 창 너비가 달라져) 보던 폴더가 밀려나므로 다시 그 폴더로
|
||
String shape = '';
|
||
|
||
/// 화면에 보일 경로 (5 · 14): Android 는 "SD 카드 › JJ_sdtest › jj_mkv", WebDAV 는 "☁ 서버 › 폴더", Windows 는 그대로
|
||
/// 그 항목이 목록 위쪽 1/4 쯤에 보이게 스크롤 (깊은 폴더로 가면 위쪽 폴더들의 다른 항목에 밀려 안 보이므로)
|
||
void reveal(ExplorerPane pane, String path) => WidgetsBinding.instance.addPostFrameCallback((_) => _revealNow(pane, path));
|
||
|
||
void _revealNow(ExplorerPane pane, String path) {
|
||
if (!ctl.host.alive || !pane.scroll.hasClients) return;
|
||
// 화면의 트리와 같은 줄로 센다 (폴더 + 파일 목록 배치 · Rsync 화면은 폴더만)
|
||
final i = ctl.rows(pane, foldersOnly: split || ctl.rsync).indexWhere((r) => r.kind == 0 && samePath(r.entry.path, path));
|
||
if (i < 0) return;
|
||
final pos = pane.scroll.position;
|
||
// 180: 끝 (maxScrollExtent) 으로 자르지 않는다 - 줄이 막 늘어난 때는 끝이 아직 앞의 것이라 보던 폴더로 못 간다.
|
||
// 넘치면 다음 배치 때 실제 끝으로 맞춰진다
|
||
pane.scroll.jumpTo((i * rowHeight() - pos.viewportDimension / 4).clamp(0.0, double.infinity));
|
||
}
|
||
|
||
/// 28 확인 (10/20): 창의 목록이 화면에 새로 붙으면 (좁은 Rsync 화면의 [오른쪽] 탭을 처음 볼 때 · 폭이 바뀌어 두 창이 될 때)
|
||
/// 그 창의 지금 폴더를 보이게 한다. 켤 때 그려지지 않은 창은 [ctl.goTo] 의 스크롤을 건너뛰어, 나중에 보면 맨 위 (저장 장치) 에 있었다
|
||
void revealNewViews() => WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
if (!ctl.host.alive) return;
|
||
for (final pane in ctl.panes) {
|
||
final on = pane.scroll.hasClients;
|
||
if (on && !pane.onScreen && pane.current.isNotEmpty) _revealNow(pane, pane.current);
|
||
pane.onScreen = on;
|
||
}
|
||
});
|
||
|
||
/// X-plore 비교 40: 목록 글자 크기 (설정 %) · 줄 간격을 넣은 줄 높이
|
||
double get listTextScale => ctl.c.settings.explorerTextScale / 100;
|
||
double rowHeight() => ctl.look.rowHeightOf(context(), textScale: listTextScale, spacing: rowSpacingFactor(ctl.c.settings.explorerRowSpacing));
|
||
|
||
/// 왼쪽 폴더 트리 + 오른쪽 파일 목록 배치
|
||
bool get split => !ctl.rsync && ctl.c.settings.explorerLayout == 'split' && !splitTight;
|
||
|
||
/// 관리자 지시 10/20 (28 검토): 글자가 커서 (기기 글자 × 목록 글자 크기) 좁은 화면에 트리 · 목록 두 창이 다 들어가지 않으면
|
||
/// 그동안만 한 창 (트리 + 위 경로 막대) 으로 보인다 - 고른 배치 (설정) 는 그대로, 화면이 넓어지거나 글자가 작아지면 다시 두 창. [ExplorerPanesBody] 가 정한다
|
||
bool splitTight = false;
|
||
|
||
/// 관리자 지시 10/20: 좁은 화면 (폭 < [narrowSplitWidth]) 의 "폴더 + 파일 목록" 좌우 배치 - 트리 창에 최소 폭을 두고
|
||
/// ✔ 칸 · 크기 줄을 숨긴다 (412 폭에서 트리가 77px 이라 폴더 이름이 한 글자도 안 보였다). [ExplorerPanesBody] 가 정한다
|
||
bool narrowSplit = false;
|
||
static const narrowSplitWidth = 600.0;
|
||
|
||
/// 좌우 두 창 (Rsync 화면은 늘)
|
||
bool get dual =>
|
||
ctl.rsync ||
|
||
ctl.c.settings.explorerLayout == 'dual' ||
|
||
// 자동: 넓으면 두 창, 좁은 화면 (폰 세로) 은 한 창 - 같은 목록이 반씩 잘려 두 번 보이지 않게
|
||
(ctl.c.settings.explorerLayout == 'auto' && !isCompact(context()));
|
||
|
||
|
||
/// 트리 줄. [foldersOnly]: 폴더만 ("폴더 + 파일 목록" 배치의 왼쪽)
|
||
/// X-plore 비교 c: 트리 맨 위에 저장 장치 · 서버 · 즐겨찾기 (X-plore 스타일 · 설정)
|
||
/// X-plore 비교 ⚖: X-plore 스타일에서 표시 동그라미 늘 보이기 (설정)
|
||
bool get alwaysMarks => !ctl.rsync && ctl.look == ExplorerStyle.xplore && ctl.c.settings.explorerAlwaysMarks;
|
||
|
||
/// X-plore 비교 ⚖: X-plore 스타일 탐색기 안에서 밀기 = 창 바꾸기 (설정)
|
||
bool get swipePanes => !ctl.rsync && ctl.look == ExplorerStyle.xplore && ctl.c.settings.explorerSwipePanes;
|
||
|
||
/// 밀어서 창 바꾸기: 두 창이면 다른 창을 활성으로, 한 창이면 숨은 창과 바꿔 보인다
|
||
bool switchPane(int dir) {
|
||
if (!swipePanes) return false;
|
||
if (dual || split) {
|
||
ctl.active = 1 - ctl.active;
|
||
} else {
|
||
final t = ctl.panes[0];
|
||
ctl.panes[0] = ctl.panes[1];
|
||
ctl.panes[1] = t;
|
||
ctl.active = 0;
|
||
}
|
||
ctl.update();
|
||
ctl.remember(ctl.pane, ctl.pane.current);
|
||
ctl.host.snack(trf('창 바꿈: {0}', [ctl.displayPath(ctl.pane.current)]));
|
||
return true;
|
||
}
|
||
|
||
/// 복사 · 이동 갈 곳 창 (두 창 · 숨은 다른 창, 없으면 null - 담아 두기)
|
||
ExplorerPane? get other => dual || hiddenTarget ? ctl.panes[1 - ctl.active] : null;
|
||
|
||
/// X-plore 비교 a: 한 창인데 "숨은 다른 창으로" 복사 · 이동 (위에 갈 곳 줄)
|
||
bool get hiddenTarget => !ctl.rsync && !dual && !split && ctl.c.settings.explorerOnePaneCopy == 'other';
|
||
}
|