JJ_MKVMaker/lib/ui/explorer/toolbar.dart

186 lines
8.0 KiB
Dart

import 'package:flutter/material.dart';
import '../../l10n/tr.dart';
import '../theme.dart';
import 'explorer_buttons.dart';
/// 약속 (E5d-4): 기능 버튼 줄 - 버튼 · 이름 · 아이콘 (창 배치 · 위아래면 "위 → 아래"), 다 들어가지 않으면 [더 보기 ⋯] (고정한 것은 늘 줄에).
/// 누르기 · 길게 누르기는 부르는 쪽 ([ExplorerButtons.press] · [ExplorerButtons.longPress]). 못 누르는 버튼은 흐리게
/// 기능 버튼 줄 (세로: 두 창 사이 · 가로: 위아래 배치일 때)
class ExplorerToolbar extends StatelessWidget {
final List<ExplorerButton> buttons;
final bool vertical;
final void Function(ExplorerButton) onPressed;
final bool Function(ExplorerButton) enabled;
final bool Function(ExplorerButton) selected;
/// 창이 둘인지 (두 창 · 폴더 + 파일 목록). 하나면 [orient] 는 "두 창으로"
final bool twoPanes;
/// 좁아도 [더 보기] 로 보내지 않는 버튼 (창 배치 · 담아 둔 것이 있을 때 붙여넣기)
final Set<ExplorerButton> pinned;
/// X-plore b: 길게 누르기 = 짝 동작 (복사 ↔ 이동, 지우기 → 영구 삭제). 짝이 없는 버튼은 null
final void Function(ExplorerButton)? onLongPress;
const ExplorerToolbar(
{super.key,
required this.buttons,
required this.vertical,
required this.onPressed,
required this.enabled,
required this.selected,
this.onLongPress,
this.twoPanes = true,
this.pinned = const {ExplorerButton.orient},
bool? panesSide})
: panesSide = panesSide ?? vertical;
/// 두 창이 좌우로 놓였는지 (버튼 이름 "좌 → 우" / "위 → 아래"). 보통 [vertical] 과 같고,
/// 191 좁은 Rsync 화면 (창 하나씩, 버튼 줄은 가로) 은 좌우
final bool panesSide;
/// 118: 지금 배치를 보여 준다 (누르면 반대로)
String _orientLabel() => !twoPanes ? '두 창으로' : panesSide ? '좌우 ⇆' : '위아래 ⇅';
String _orientTip() => !twoPanes
? '두 창으로 보기 (좌우)'
: panesSide
? '창 배치: 좌우 (누르면 위아래)'
: '창 배치: 위아래 (누르면 좌우)';
/// 창이 위아래로 놓이면 (버튼 줄이 가로) "좌 → 우" 대신 "위 → 아래"
String _label(ExplorerButton b) => b == ExplorerButton.orient
? _orientLabel()
: panesSide
? b.label
: switch (b) {
ExplorerButton.toRight => '위 → 아래',
ExplorerButton.toLeft => '위 ← 아래',
ExplorerButton.both => '위 ⇄ 아래',
_ => b.label,
};
IconData _icon(ExplorerButton b) => b == ExplorerButton.orient
? (!twoPanes ? Icons.vertical_split_outlined : panesSide ? Icons.swap_horiz : Icons.swap_vert)
: panesSide
? b.icon
: switch (b) {
ExplorerButton.toRight => Icons.arrow_downward,
ExplorerButton.toLeft => Icons.arrow_upward,
ExplorerButton.both => Icons.swap_vert,
_ => b.icon,
};
@override
Widget build(BuildContext context) {
Widget btn(ExplorerButton b) {
final on = enabled(b);
final sel = selected(b);
final pair = ExplorerButtons.longPressLabel(b);
final long = onLongPress;
return Tooltip(
message: tr(b == ExplorerButton.orient ? _orientTip() : _label(b)) +
(pair != null && long != null ? ' · ${trf('길게 누르면 {0}', [tr(pair)])}' : ''),
child: InkWell(
onTap: on ? () => onPressed(b) : null,
onLongPress: on && pair != null && long != null ? () => long(b) : null,
borderRadius: BorderRadius.circular(6),
child: SizedBox(
width: 72,
height: 58,
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Icon(_icon(b), size: 22, color: !on ? JjColors.textDim.withValues(alpha: 0.4) : sel ? JjColors.accent : null),
const SizedBox(height: 2),
// 188: 두 줄로 꺾인 글 (일본어 "新しいフォルダー" 등) 이 버튼 아래로 잘렸다 - 남는 높이에 맞게 줄여 다 보이게
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: SizedBox(
width: 68,
child: Text(tr(_label(b)),
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 10.5, height: 1.1, color: on ? null : JjColors.textDim)),
),
),
),
]),
),
),
);
}
// 147: 다 들어가지 않으면 (좁은 · 낮은 화면) 들어가는 만큼 보이고 나머지는 [더 보기 ⋯] 메뉴로 - 밀어야 보이는 것을 몰라 못 찾던 것
Widget more(List<ExplorerButton> rest) => Builder(
builder: (context) => Tooltip(
message: tr('더 보기'),
child: InkWell(
borderRadius: BorderRadius.circular(6),
onTap: () async {
final box = context.findRenderObject()! as RenderBox;
final at = box.localToGlobal(Offset.zero);
final pick = await showMenu<ExplorerButton>(
context: context,
position: RelativeRect.fromLTRB(at.dx + box.size.width, at.dy, at.dx + box.size.width, at.dy),
items: [
for (final b in rest)
PopupMenuItem(
value: b,
enabled: enabled(b),
height: 40,
child: Row(children: [
Icon(_icon(b), size: 18, color: selected(b) ? JjColors.accent : null),
const SizedBox(width: 10),
Text(tr(_label(b))),
]),
),
],
);
if (pick != null) onPressed(pick);
},
child: SizedBox(
width: 72,
height: 58,
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
const Icon(Icons.more_horiz, size: 22),
const SizedBox(height: 2),
Text(tr('더 보기'), style: const TextStyle(fontSize: 10.5, height: 1.1)),
]),
),
),
),
);
return Container(
color: JjColors.panel,
width: vertical ? 76 : null,
height: vertical ? null : 62,
padding: const EdgeInsets.all(2),
child: LayoutBuilder(builder: (context, box) {
final room = vertical ? box.maxHeight : box.maxWidth;
final each = vertical ? 58.0 : 72.0;
var shown = buttons;
var rest = const <ExplorerButton>[];
if (room.isFinite && buttons.length * each > room) {
final fit = (room / each).floor() - 1; // 하나는 [더 보기] 자리
if (fit >= 1) {
shown = buttons.take(fit).toList();
rest = buttons.skip(fit).toList();
// 창 배치 버튼은 늘 줄에 (사용자가 가운데 아이콘 모음에 두라고 함, 10/9) - 넘치면 마지막 자리를 내준다.
// 73 (관리자): 복사 · 이동으로 담아 둔 것이 있으면 [붙여넣기] 도 (담아 두고 붙여넣기를 찾지 못하는 일이 없게)
for (final b in buttons.where((b) => pinned.contains(b) && rest.contains(b))) {
final i = shown.lastIndexWhere((x) => !pinned.contains(x));
if (i < 0) break;
final out = shown[i];
shown = [...shown.sublist(0, i), ...shown.sublist(i + 1), b];
rest = [out, ...rest.where((x) => x != b)];
}
}
}
final children = [for (final b in shown) btn(b), if (rest.isNotEmpty) more(rest)];
return vertical ? Column(children: children) : Row(children: children);
}),
);
}
}