GALAXY 맵에서 고른 행성을 카메라가 따라가 화면 가운데에 유지
This commit is contained in:
@@ -172,6 +172,27 @@ class _GalaxyScreenState extends State<GalaxyScreen>
|
||||
_lastTick = now;
|
||||
// 프레임을 오래 건너뛴 뒤(백그라운드 복귀 등) 한 번에 확 도는 것을 막는다.
|
||||
_clock.advance(dt.clamp(0.0, 1 / 20));
|
||||
_follow();
|
||||
}
|
||||
|
||||
/// 고른 행성을 화면 가운데에 붙든다(프로토타입 `galaxyGroup.position = -myPos` 추적).
|
||||
/// 중심 전환이 끝난 뒤에도 궤도는 공전·끌기 관성으로 계속 도므로, 한 번 옮기고 말면
|
||||
/// 고른 행성이 화면 밖으로 흘러가고 장면이 밀린 채 남는다(10/5 실기기). 배율은 사용자
|
||||
/// 핀치 값을 그대로 두고 평행 이동만 맞춘다.
|
||||
void _follow() {
|
||||
final p = _selected;
|
||||
if (p == null || _viewport.isEmpty || _fly.isAnimating) return;
|
||||
final at = GalaxyField.positionOf(p, _selectedIndex, _clock.spin, _outerRank);
|
||||
final s = _view.value.getMaxScaleOnAxis();
|
||||
final tx = _viewport.width / 2 - at.dx * s;
|
||||
final ty = _viewport.height / 2 - at.dy * s;
|
||||
final m = _view.value;
|
||||
if ((m.storage[12] - tx).abs() < 0.01 && (m.storage[13] - ty).abs() < 0.01) {
|
||||
return;
|
||||
}
|
||||
_view.value = m.clone()
|
||||
..setEntry(0, 3, tx)
|
||||
..setEntry(1, 3, ty);
|
||||
}
|
||||
|
||||
/// 선택 행성을 화면 한가운데로 데려오는 변환.
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:fancake/features/galaxy/galaxy_screen.dart';
|
||||
import 'package:fancake/features/galaxy/galaxy_search_screen.dart';
|
||||
import 'package:fancake/features/galaxy/widgets/zap_button.dart';
|
||||
import 'package:fancake/features/planet/planet_data.dart';
|
||||
import 'package:fancake/features/planet/planet_detail_screen.dart';
|
||||
|
||||
import 'eclipse_stubs.dart';
|
||||
|
||||
@@ -33,6 +34,24 @@ Future<void> _pump(WidgetTester tester, Widget screen) async {
|
||||
await tester.pump(const Duration(milliseconds: 950)); // 이벤트 띠 슬라이드
|
||||
}
|
||||
|
||||
/// 두 손가락으로 끝까지 확대하며 오른쪽 아래로 끈다(한 손가락 팬은 없다).
|
||||
Future<void> _pinch(WidgetTester tester) async {
|
||||
final origin = tester.getTopLeft(find.byType(InteractiveViewer));
|
||||
final a = await tester.startGesture(origin + const Offset(4, 4));
|
||||
final b = await tester.startGesture(
|
||||
origin + const Offset(24, 24),
|
||||
pointer: 7,
|
||||
);
|
||||
for (var i = 1; i <= 10; i++) {
|
||||
await b.moveTo(origin + Offset(24.0 + i * 32, 24.0 + i * 32));
|
||||
await a.moveTo(origin + Offset(4.0 + i * 20, 4.0 + i * 20));
|
||||
await tester.pump();
|
||||
}
|
||||
await a.up();
|
||||
await b.up();
|
||||
await _settle(tester);
|
||||
}
|
||||
|
||||
/// 회전이 도는 동안에도 안전하게 몇 프레임 넘긴다.
|
||||
Future<void> _settle(WidgetTester tester) async {
|
||||
for (var i = 0; i < 6; i++) {
|
||||
@@ -176,17 +195,16 @@ void main() {
|
||||
tester.view.devicePixelRatio = 2.0;
|
||||
addTearDown(tester.view.reset);
|
||||
await _pump(tester, _galaxy());
|
||||
// 가운데 내 행성은 궤도를 돌지 않는다 — 궤도 위 행성을 골라 돌린다.
|
||||
await tester.tap(find.byKey(const ValueKey('newjeans')));
|
||||
await _settle(tester);
|
||||
final before = tester.getCenter(find.text('RANK'));
|
||||
await tester.drag(find.byType(InteractiveViewer), const Offset(-200, 0));
|
||||
await _settle(tester);
|
||||
// 확대하면 행성이 커지고, 행성 위에 붙은 카드는 행성 가장자리를 따라 밀려난다.
|
||||
await _pinch(tester);
|
||||
final after = tester.getCenter(find.text('RANK'));
|
||||
expect(
|
||||
(after - before).distance,
|
||||
greaterThan(20),
|
||||
reason: '화면 고정 패널이면 맵을 끌어도 제자리에 있다',
|
||||
reason: '화면 고정 패널이면 확대해도 제자리에 있다',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -277,31 +295,63 @@ void main() {
|
||||
|
||||
// 리뷰 M2 — 행성이 뷰포트 밖이면 HUD 를 아예 그리지 않는다. 카드만 가장자리에
|
||||
// clamp 해 두면 어느 행성을 가리키는지 알 수 없고 연결선은 화면 밖에 그려진다.
|
||||
testWidgets('행성이 화면 밖으로 나가면 HUD 도 사라진다', (tester) async {
|
||||
// 고른 행성은 카메라가 따라가므로(10/5) 확대·초점 이동으로도 화면 밖으로 밀리지 않는다.
|
||||
testWidgets('확대해도 고른 행성은 가운데에 남고 HUD 도 남는다', (tester) async {
|
||||
await _pump(tester, _galaxy());
|
||||
final mid = tester.getCenter(find.byType(InteractiveViewer));
|
||||
await _pinch(tester);
|
||||
expect(find.text('RANK'), findsOneWidget);
|
||||
final mine = tester.widget<InteractiveViewer>(find.byType(InteractiveViewer));
|
||||
expect(mine.transformationController!.value.getMaxScaleOnAxis(),
|
||||
greaterThan(GalaxyMotion.focusZoom));
|
||||
final at = tester.getCenter(find.text('RANK'));
|
||||
expect((at.dx - mid.dx).abs(), lessThan(mid.dx), reason: '카드가 화면 안');
|
||||
});
|
||||
|
||||
// 끝까지 확대하며 두 손가락을 오른쪽 아래로 끌어 내 행성을 화면 밖으로 보낸다
|
||||
// (한 손가락 팬은 없다 — 확대 제스처의 초점 이동만 맵을 옮긴다).
|
||||
// 10/5 실기기(Flip3) — 다른 행성을 탭하면 중심 전환 뒤에도 궤도가 돌아 고른 행성이
|
||||
// 화면 밖으로 흘러가고 내 행성은 가장자리에 남았다. 명세 「행성 터치시 화면 중심 전환」.
|
||||
testWidgets('고른 행성은 공전·끌기 회전 중에도 화면 가운데에 머문다', (tester) async {
|
||||
tester.view.physicalSize = const Size(1200, 2000);
|
||||
tester.view.devicePixelRatio = 2.0;
|
||||
addTearDown(tester.view.reset);
|
||||
await _pump(tester, _galaxy());
|
||||
final viewer = find.byType(InteractiveViewer);
|
||||
final origin = tester.getTopLeft(viewer);
|
||||
final a = await tester.startGesture(origin + const Offset(4, 4));
|
||||
final b = await tester.startGesture(
|
||||
origin + const Offset(24, 24),
|
||||
pointer: 7,
|
||||
final mid = tester.getCenter(viewer);
|
||||
final planet = find.byKey(const ValueKey('newjeans'));
|
||||
|
||||
await tester.tap(planet);
|
||||
for (var i = 0; i < 25; i++) {
|
||||
await tester.pump(const Duration(milliseconds: 120)); // 3초 공전
|
||||
}
|
||||
expect((tester.getCenter(planet) - mid).distance, lessThan(8));
|
||||
|
||||
await tester.fling(viewer, const Offset(-300, 0), 1500);
|
||||
for (var i = 0; i < 25; i++) {
|
||||
await tester.pump(const Duration(milliseconds: 120));
|
||||
}
|
||||
expect(
|
||||
(tester.getCenter(planet) - mid).distance,
|
||||
lessThan(8),
|
||||
reason: '관성 회전 뒤에도 고른 행성이 가운데',
|
||||
);
|
||||
for (var i = 1; i <= 10; i++) {
|
||||
await b.moveTo(origin + Offset(24.0 + i * 32, 24.0 + i * 32));
|
||||
await a.moveTo(origin + Offset(4.0 + i * 20, 4.0 + i * 20));
|
||||
await tester.pump();
|
||||
});
|
||||
|
||||
// 10/5 실기기 — HUD 카드 「>」를 눌러도 행성 상세로 가지 않았다.
|
||||
// 명세 「HUD 터치시 FPI 상세 페이지 이동」.
|
||||
testWidgets('HUD 카드의 화살표를 누르면 고른 행성의 상세로 간다', (tester) async {
|
||||
tester.view.physicalSize = const Size(1200, 2000);
|
||||
tester.view.devicePixelRatio = 2.0;
|
||||
addTearDown(tester.view.reset);
|
||||
await _pump(tester, _galaxy());
|
||||
await tester.tap(find.byKey(const ValueKey('newjeans')));
|
||||
for (var i = 0; i < 10; i++) {
|
||||
await tester.pump(const Duration(milliseconds: 120));
|
||||
}
|
||||
await a.up();
|
||||
await b.up();
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await _settle(tester);
|
||||
expect(find.text('RANK'), findsNothing);
|
||||
expect(find.byType(PlanetDetailScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
// 10/4 실기기 — 가로로 끌면 궤도가 돌지 않고 장면이 통째로 밀려 빈 화면이 됐다.
|
||||
testWidgets('가로로 끌면 장면은 제자리에서 궤도가 돌고, 손을 떼도 관성으로 더 돈다', (tester) async {
|
||||
await _pump(tester, _galaxy());
|
||||
final viewer = tester.widget<InteractiveViewer>(
|
||||
|
||||
Reference in New Issue
Block a user