186 lines
8.0 KiB
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);
|
|
}),
|
|
);
|
|
}
|
|
}
|