Skip to content

Commit 2782f6c

Browse files
committed
Add zoom gestures
1 parent 22bdc3b commit 2782f6c

6 files changed

Lines changed: 68 additions & 35 deletions

File tree

app/lib/board/cell.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ class GameCell extends PositionComponent
189189
}
190190

191191
VectorDefinition toDefinition() =>
192-
(position.clone()..divide(grid.cellSizeWithZoom)).toDefinition();
192+
(position.clone()..divide(grid.cellSize)).toDefinition();
193193

194194
GlobalVectorDefinition toGlobalDefinition(ClientWorldState state) =>
195195
GlobalVectorDefinition.fromLocal(state.tableName, toDefinition());

app/lib/board/game.dart

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:flame/components.dart';
44
import 'package:flame/events.dart';
55
import 'package:flame/game.dart';
66
import 'package:flame_bloc/flame_bloc.dart';
7+
import 'package:flutter/foundation.dart';
78
import 'package:flutter/services.dart';
89
import 'package:flutter/widgets.dart';
910
import 'package:setonix/bloc/settings.dart';
@@ -17,7 +18,7 @@ import 'package:setonix/helpers/vector.dart';
1718
import 'package:setonix_api/setonix_api.dart';
1819

1920
class 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

app/lib/board/grid.dart

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
11
import 'package:flame/components.dart';
22
import 'package:flame/extensions.dart';
3-
import 'package:flame_bloc/flame_bloc.dart';
4-
import 'package:setonix/bloc/settings.dart';
53
import 'package:setonix/board/cell.dart';
64

7-
class BoardGrid extends PositionComponent
8-
with HasGameReference, FlameBlocListenable<SettingsCubit, SetonixSettings> {
5+
class BoardGrid extends PositionComponent with HasGameReference {
96
final Vector2 cellSize;
107
static const _padding = 3.0;
118
Rect? _lastViewport;
12-
double _zoom = 1.0;
139

1410
BoardGrid({required this.cellSize});
1511

1612
Rect get viewport {
1713
final Rect viewport = game.camera.visibleWorldRect;
18-
final currentSize = cellSizeWithZoom;
14+
final currentSize = cellSize;
1915
return Rect.fromLTRB(
2016
(viewport.left / currentSize.x - _padding).floor() * currentSize.x,
2117
(viewport.top / currentSize.y - _padding).floor() * currentSize.y,
@@ -35,7 +31,7 @@ class BoardGrid extends PositionComponent
3531
void _updateGrid() {
3632
if (!shouldReset()) return;
3733
final viewport = this.viewport;
38-
final currentSize = cellSizeWithZoom;
34+
final currentSize = cellSize;
3935
// Remove components that are out of the viewport
4036
removeAll(
4137
children.where((element) {
@@ -67,8 +63,6 @@ class BoardGrid extends PositionComponent
6763
_lastViewport = viewport;
6864
}
6965

70-
Vector2 get cellSizeWithZoom => cellSize * _zoom;
71-
7266
@override
7367
void update(double dt) {
7468
super.update(dt);
@@ -83,22 +77,4 @@ class BoardGrid extends PositionComponent
8377

8478
Component _createCell({required Vector2 position, required Vector2 size}) =>
8579
GameCell(position: position, size: size);
86-
87-
@override
88-
void onInitialState(SetonixSettings state) {
89-
_zoom = state.zoom;
90-
}
91-
92-
@override
93-
bool listenWhen(SetonixSettings previousState, SetonixSettings newState) =>
94-
previousState.zoom != newState.zoom;
95-
96-
@override
97-
void onNewState(SetonixSettings state) {
98-
if (_zoom != state.zoom) {
99-
_zoom = state.zoom;
100-
_lastViewport = null;
101-
removeAll(children);
102-
}
103-
}
10480
}

app/pubspec.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -414,10 +414,10 @@ packages:
414414
dependency: "direct main"
415415
description:
416416
name: flame
417-
sha256: "348136b7d928188a8c9a11cd844d9125b02e7afc6c83bb9a52a16aee88ab7e81"
417+
sha256: "6a98c1697bfe4669c604e4459cb01ca96559f464d3523f6af90a8956bf2080ee"
418418
url: "https://pub.dev"
419419
source: hosted
420-
version: "1.34.0"
420+
version: "1.35.0"
421421
flame_bloc:
422422
dependency: "direct main"
423423
description:

app/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ dependencies:
4747
rxdart: ^0.28.0
4848
collection: ^1.18.0
4949
flame_bloc: ^1.12.14
50-
flame: ">=1.34.0 <1.35.0"
50+
flame: ">=1.35.0 <1.36.0"
5151
flame_tiled: ^3.0.4
5252
# Networking
5353
http: ^1.2.1

metadata/en-US/changelogs/11.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
* Add zoom gestures
12
* Fix server builds not created for released versions
23

34
Read more here: https://linwood.dev/setonix/0.6.1

0 commit comments

Comments
 (0)