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 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 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 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( 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 []; 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); }), ); } }