/// 한 줄 · 두 줄 배치 판단 (관리자 지시 10/21: 화면마다 따로 재지 않고 이것 하나를 부른다 - 82a87da P2, after_1020 에서 옮김). /// /// 약속 ([fitRow] - 판단만, 그리지 않음) /// - 입력: [maxWidth] 쓸 수 있는 폭, [first] · [second] 앞 · 뒤 묶음 아이템의 최소 폭 (단추는 기본 누를 자리 그대로), /// [gap] 한 줄일 때 두 묶음 사이, 줄일 수 있는 아이템 하나의 폭 범위 [flexMin] ~ [flexMax] (없으면 0 · 0). /// - 출력: [RowFit.split] 이면 두 줄 (위 = first, 아래 = second + 줄일 수 있는 아이템), [RowFit.flexWidth] 줄일 수 있는 아이템의 폭 /// (0 이면 그 줄에 넣지 않는다 - 같은 일을 하는 다른 단추가 남아 있어야 한다). /// - 오류: 없음. 두 줄로도 [second] 가 들지 않으면 split = true · flexWidth = 0 이고 [RowFit.overflow] 에 모자란 폭. /// /// 약속 ([FitButtonRows] - 그림): 앞 단추들 · 뒤 앞 단추들 · 줄일 수 있는 것 (폭을 받아 그림) · 뒤 끝 단추들 → 한 줄 /// (앞 · 간격 · 뒤 앞 · 줄일 것 · 빈칸 · 뒤 끝) 또는 두 줄 (위 앞 단추 고르게, 아래 뒤 묶음). 단추 크기는 줄이지 않는다. /// 쓰는 곳: 재생 화면 조절 줄 (P2) library; import 'package:flutter/material.dart'; class RowFit { final bool split; final double flexWidth; final double overflow; const RowFit(this.split, this.flexWidth, this.overflow); } RowFit fitRow({ required double maxWidth, required List first, required List second, double gap = 0, double flexMin = 0, double flexMax = 0, bool forceSplit = false, }) { double sum(List xs) => xs.fold(0.0, (a, b) => a + b); final a = sum(first), b = sum(second); final split = forceSplit || a + gap + b + flexMax > maxWidth; final room = maxWidth - b - (split ? 0 : a + gap); var flex = room.clamp(0.0, flexMax); if (flex < flexMin) flex = 0; final over = (split ? [a, b].reduce((x, y) => x > y ? x : y) : a + gap + b) - maxWidth; return RowFit(split, flex, over > 0 ? over : 0); } class FitButtonRows extends StatelessWidget { const FitButtonRows({ super.key, required this.first, required this.secondLead, required this.secondTail, required this.buttonWidth, this.flex, this.flexMin = 0, this.flexMax = 0, this.gap = 8, this.forceSplit = false, }); /// 위 줄 (두 줄일 때) · 앞 (한 줄일 때) 단추들 final List first; /// 뒤 묶음의 앞 (줄일 것 앞) · 끝 (빈칸 뒤) 단추들 final List secondLead, secondTail; /// 단추 하나의 누를 자리 (판단에 쓰는 폭) final double buttonWidth; /// 줄일 수 있는 것 (받은 폭으로 그림 - 예: 소리 막대). 폭이 0 이면 넣지 않는다 final Widget Function(double width)? flex; final double flexMin, flexMax, gap; /// 늘 두 줄 (손가락 화면) final bool forceSplit; @override Widget build(BuildContext context) => LayoutBuilder(builder: (context, box) { final fit = fitRow( maxWidth: box.maxWidth, first: List.filled(first.length, buttonWidth), second: List.filled(secondLead.length + secondTail.length, buttonWidth), gap: gap, flexMin: flex == null ? 0 : flexMin, flexMax: flex == null ? 0 : flexMax, forceSplit: forceSplit, ); final rest = [ ...secondLead, if (flex != null && fit.flexWidth > 0) flex!(fit.flexWidth), const Spacer(), ...secondTail, ]; return Column(mainAxisSize: MainAxisSize.min, children: [ if (fit.split) Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: first), Row(children: [ if (!fit.split) ...[...first, SizedBox(width: gap)], ...rest, ]), ]); }); }