@@ -4,6 +4,7 @@ import 'package:flame/components.dart';
44import 'package:flame/events.dart' ;
55import 'package:flame/game.dart' ;
66import 'package:flame_bloc/flame_bloc.dart' ;
7+ import 'package:flutter/foundation.dart' ;
78import 'package:flutter/services.dart' ;
89import 'package:flutter/widgets.dart' ;
910import 'package:setonix/bloc/settings.dart' ;
@@ -17,7 +18,7 @@ import 'package:setonix/helpers/vector.dart';
1718import 'package:setonix_api/setonix_api.dart' ;
1819
1920class BoardGame extends FlameGame
20- with ScrollDetector , KeyboardEvents , HasCollisionDetection {
21+ with ScrollDetector , KeyboardEvents , HasCollisionDetection , ScaleDetector {
2122 final VoidCallback onEscape;
2223 final ContextMenuController contextMenuController;
2324 late final Sprite selectionSprite, blankSprite;
@@ -45,10 +46,20 @@ class BoardGame extends FlameGame
4546 await add (
4647 FlameBlocProvider <SettingsCubit , SetonixSettings >.value (
4748 value: settingsCubit,
48- children: [provider],
49+ children: [
50+ provider,
51+ FlameBlocListener <SettingsCubit , SetonixSettings >(
52+ listenWhen: (previousState, newState) =>
53+ previousState.zoom != newState.zoom,
54+ onNewState: (state) {
55+ camera.viewfinder.zoom = state.zoom.clamp (0.4 , 2.0 );
56+ },
57+ ),
58+ ],
4959 ),
5060 );
5161 provider.addAll ([camera, world]);
62+ camera.viewfinder.zoom = settingsCubit.state.zoom;
5263 selectionSprite = await Sprite .load ('selection.png' );
5364 blankSprite = await Sprite .load ('blank.png' );
5465 _hand = GameHand ();
@@ -74,11 +85,55 @@ class BoardGame extends FlameGame
7485
7586 static const zoomPerScrollUnit = 0.02 ;
7687
88+ double _startZoom = 1.0 ;
89+ Vector2 _startPosition = Vector2 .zero ();
90+
91+ @override
92+ void onScaleStart (ScaleStartInfo info) {
93+ _startZoom = camera.viewfinder.zoom;
94+ _startPosition = info.eventPosition.global;
95+ }
96+
97+ @override
98+ void onScaleUpdate (ScaleUpdateInfo info) {
99+ final zoom = camera.viewfinder.zoom;
100+ final delta = (info.delta.global..negate ()) / zoom;
101+
102+ if (info.scale.global.x == 0 || info.scale.global.x == 1.0 ) {
103+ camera.moveBy (delta);
104+ return ;
105+ }
106+
107+ final newZoom = (_startZoom * info.scale.global.x).clamp (0.4 , 2.0 );
108+ final oldZoom = camera.viewfinder.zoom.clamp (0.4 , 2.0 );
109+ camera.viewfinder.zoom = newZoom;
110+ // Zoom inside towards the focal point
111+ final screenCenter = camera.viewport.size / 2 ;
112+ final zoomDelta = newZoom - oldZoom;
113+ if (((4.0 - newZoom) < precisionErrorTolerance &&
114+ (4.0 - oldZoom) < precisionErrorTolerance) ||
115+ ((0.4 - newZoom) < precisionErrorTolerance &&
116+ (0.4 - oldZoom) < precisionErrorTolerance)) {
117+ return ;
118+ }
119+ final focalPoint = info.eventPosition.global - screenCenter;
120+ final focalDelta = (focalPoint - _startPosition) / newZoom;
121+ camera.moveBy (focalDelta * (zoomDelta / newZoom) + delta);
122+ }
123+
124+ @override
125+ void onScaleEnd (ScaleEndInfo info) {
126+ settingsCubit.resetZoom (camera.viewfinder.zoom);
127+ }
128+
77129 @override
78130 void onScroll (PointerScrollInfo info) {
79- componentsAtPoint (
131+ final handled = componentsAtPoint (
80132 info.eventPosition.widget,
81133 ).whereType <ScrollCallbacks >().any ((element) => element.onScroll (info));
134+ if (handled) return ;
135+ camera.viewfinder.zoom +=
136+ info.scrollDelta.global.y.sign * zoomPerScrollUnit;
82137 }
83138
84139 Vector2 _currentCameraVelocity = Vector2 .zero ();
@@ -88,7 +143,8 @@ class BoardGame extends FlameGame
88143 super .update (dt);
89144
90145 if (! _currentCameraVelocity.isZero ()) {
91- camera.moveBy (_currentCameraVelocity * dt * 60 );
146+ final zoom = camera.viewfinder.zoom;
147+ camera.moveBy (_currentCameraVelocity * dt * 500 / zoom);
92148 }
93149 }
94150
0 commit comments