JJ_MKVMaker/lib/ui/scroll_more.dart

104 lines
3.4 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import 'package:flutter/material.dart';
import '../l10n/tr.dart';
import 'theme.dart';
/// 옆으로 미는 줄 (폰 세로의 버튼 줄 등) 에 "더 있음" 표시.
/// 줄이 화면보다 길면 끝 쪽에 흐려지는 덮개와 [›] (왼쪽으로 밀었으면 [‹]) 를 보이고, 누르면 그쪽으로 한 화면쯤 민다.
/// (MKV 화면 폭 412: 세 번째 버튼이 화면 밖에서 잘렸는데 더 있다는 표시가 없었다 - 15 지적)
class ScrollMore extends StatefulWidget {
final Widget child;
/// 덮개 색 (줄 뒤 바탕과 같게)
final Color? background;
const ScrollMore({super.key, required this.child, this.background});
@override
State<ScrollMore> createState() => _ScrollMoreState();
}
class _ScrollMoreState extends State<ScrollMore> {
final _scroll = ScrollController();
bool _left = false, _right = false;
@override
void initState() {
super.initState();
_scroll.addListener(_update);
WidgetsBinding.instance.addPostFrameCallback((_) => _update());
}
@override
void dispose() {
_scroll.dispose();
super.dispose();
}
void _update() {
if (!mounted || !_scroll.hasClients) return;
final p = _scroll.position;
final left = p.pixels > 1;
final right = p.pixels < p.maxScrollExtent - 1;
if (left != _left || right != _right) {
setState(() {
_left = left;
_right = right;
});
}
}
void _jump(int dir) {
final p = _scroll.position;
final to = (p.pixels + dir * p.viewportDimension * 0.7).clamp(0.0, p.maxScrollExtent);
_scroll.animateTo(to, duration: const Duration(milliseconds: 220), curve: Curves.easeOut);
}
/// 끝의 [›] · [‹]: 짧은 흐림 + 바탕을 깐 단추 (15 지적: 잘린 버튼 글자가 [›] 뒤로 비쳐 보였다)
Widget _edge(bool right) {
final bg = widget.background ?? Theme.of(context).scaffoldBackgroundColor;
final fade = Container(
width: 20,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: right ? Alignment.centerLeft : Alignment.centerRight,
end: right ? Alignment.centerRight : Alignment.centerLeft,
colors: [bg.withValues(alpha: 0), bg],
),
),
);
final button = ColoredBox(
color: bg,
child: Center(
child: IconButton(
tooltip: right ? tr('버튼 더 보기') : tr('앞의 버튼 보기'),
visualDensity: VisualDensity.compact,
style: IconButton.styleFrom(backgroundColor: JjColors.panelHigh),
icon: Icon(right ? Icons.chevron_right : Icons.chevron_left, color: JjColors.accent),
onPressed: () => _jump(right ? 1 : -1),
),
),
);
return Positioned(
top: 0,
bottom: 0,
left: right ? null : 0,
right: right ? 0 : null,
child: Row(mainAxisSize: MainAxisSize.min, children: right ? [fade, button] : [button, fade]),
);
}
@override
Widget build(BuildContext context) => NotificationListener<ScrollMetricsNotification>(
// 줄 안의 버튼 글이 바뀌어 길이가 달라져도 다시 본다
onNotification: (_) {
WidgetsBinding.instance.addPostFrameCallback((_) => _update());
return false;
},
child: Stack(children: [
SingleChildScrollView(controller: _scroll, scrollDirection: Axis.horizontal, child: widget.child),
if (_left) _edge(false),
if (_right) _edge(true),
]),
);
}