Skip to content

Commit 080a6ed

Browse files
committed
Add zoom
1 parent b532b6b commit 080a6ed

File tree

12 files changed

+233
-26
lines changed

12 files changed

+233
-26
lines changed

app/lib/bloc/world/bloc.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ class WorldBloc extends Bloc<PlayableWorldEvent, ClientWorldState> {
8989
on<DrawerViewChanged>((event, emit) {
9090
emit(state.copyWith(drawerView: event.view));
9191
});
92+
on<ZoomChanged>((event, emit) {
93+
var zoom = event.zoom;
94+
if (zoom != null) {
95+
zoom += state.zoom;
96+
zoom = zoom.clamp(0.5, 2.0);
97+
}
98+
zoom ??= 1;
99+
emit(state.copyWith(zoom: zoom));
100+
});
92101
}
93102

94103
Future<void> save() async {

app/lib/bloc/world/local.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,11 @@ final class DrawerViewChanged extends LocalWorldEvent
5858

5959
DrawerViewChanged(this.view);
6060
}
61+
62+
@MappableClass()
63+
final class ZoomChanged extends LocalWorldEvent with ZoomChangedMappable {
64+
final double? zoom;
65+
66+
ZoomChanged(this.zoom);
67+
ZoomChanged.reset() : zoom = null;
68+
}

app/lib/bloc/world/local.mapper.dart

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,3 +727,115 @@ class _DrawerViewChangedCopyWithImpl<$R, $Out>
727727
Then<$Out2, $R2> t) =>
728728
_DrawerViewChangedCopyWithImpl($value, $cast, t);
729729
}
730+
731+
class ZoomChangedMapper extends SubClassMapperBase<ZoomChanged> {
732+
ZoomChangedMapper._();
733+
734+
static ZoomChangedMapper? _instance;
735+
static ZoomChangedMapper ensureInitialized() {
736+
if (_instance == null) {
737+
MapperContainer.globals.use(_instance = ZoomChangedMapper._());
738+
LocalWorldEventMapper.ensureInitialized().addSubMapper(_instance!);
739+
}
740+
return _instance!;
741+
}
742+
743+
@override
744+
final String id = 'ZoomChanged';
745+
746+
static double? _$zoom(ZoomChanged v) => v.zoom;
747+
static const Field<ZoomChanged, double> _f$zoom = Field('zoom', _$zoom);
748+
749+
@override
750+
final MappableFields<ZoomChanged> fields = const {
751+
#zoom: _f$zoom,
752+
};
753+
754+
@override
755+
final String discriminatorKey = 'type';
756+
@override
757+
final dynamic discriminatorValue = 'ZoomChanged';
758+
@override
759+
late final ClassMapperBase superMapper =
760+
LocalWorldEventMapper.ensureInitialized();
761+
762+
static ZoomChanged _instantiate(DecodingData data) {
763+
return ZoomChanged(data.dec(_f$zoom));
764+
}
765+
766+
@override
767+
final Function instantiate = _instantiate;
768+
769+
static ZoomChanged fromMap(Map<String, dynamic> map) {
770+
return ensureInitialized().decodeMap<ZoomChanged>(map);
771+
}
772+
773+
static ZoomChanged fromJson(String json) {
774+
return ensureInitialized().decodeJson<ZoomChanged>(json);
775+
}
776+
}
777+
778+
mixin ZoomChangedMappable {
779+
String toJson() {
780+
return ZoomChangedMapper.ensureInitialized()
781+
.encodeJson<ZoomChanged>(this as ZoomChanged);
782+
}
783+
784+
Map<String, dynamic> toMap() {
785+
return ZoomChangedMapper.ensureInitialized()
786+
.encodeMap<ZoomChanged>(this as ZoomChanged);
787+
}
788+
789+
ZoomChangedCopyWith<ZoomChanged, ZoomChanged, ZoomChanged> get copyWith =>
790+
_ZoomChangedCopyWithImpl(this as ZoomChanged, $identity, $identity);
791+
@override
792+
String toString() {
793+
return ZoomChangedMapper.ensureInitialized()
794+
.stringifyValue(this as ZoomChanged);
795+
}
796+
797+
@override
798+
bool operator ==(Object other) {
799+
return ZoomChangedMapper.ensureInitialized()
800+
.equalsValue(this as ZoomChanged, other);
801+
}
802+
803+
@override
804+
int get hashCode {
805+
return ZoomChangedMapper.ensureInitialized().hashValue(this as ZoomChanged);
806+
}
807+
}
808+
809+
extension ZoomChangedValueCopy<$R, $Out>
810+
on ObjectCopyWith<$R, ZoomChanged, $Out> {
811+
ZoomChangedCopyWith<$R, ZoomChanged, $Out> get $asZoomChanged =>
812+
$base.as((v, t, t2) => _ZoomChangedCopyWithImpl(v, t, t2));
813+
}
814+
815+
abstract class ZoomChangedCopyWith<$R, $In extends ZoomChanged, $Out>
816+
implements LocalWorldEventCopyWith<$R, $In, $Out> {
817+
@override
818+
$R call({double? zoom});
819+
ZoomChangedCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
820+
}
821+
822+
class _ZoomChangedCopyWithImpl<$R, $Out>
823+
extends ClassCopyWithBase<$R, ZoomChanged, $Out>
824+
implements ZoomChangedCopyWith<$R, ZoomChanged, $Out> {
825+
_ZoomChangedCopyWithImpl(super.value, super.then, super.then2);
826+
827+
@override
828+
late final ClassMapperBase<ZoomChanged> $mapper =
829+
ZoomChangedMapper.ensureInitialized();
830+
@override
831+
$R call({Object? zoom = $none}) =>
832+
$apply(FieldCopyWithData({if (zoom != $none) #zoom: zoom}));
833+
@override
834+
ZoomChanged $make(CopyWithData data) =>
835+
ZoomChanged(data.get(#zoom, or: $value.zoom));
836+
837+
@override
838+
ZoomChangedCopyWith<$R2, ZoomChanged, $Out2> $chain<$R2, $Out2>(
839+
Then<$Out2, $R2> t) =>
840+
_ZoomChangedCopyWithImpl($value, $cast, t);
841+
}

app/lib/bloc/world/state.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ final class ClientWorldState extends WorldState with ClientWorldStateMappable {
2929
final ItemLocation? selectedDeck;
3030
final bool showHand, switchCellOnMove;
3131
final DrawerView drawerView;
32+
final double zoom;
3233

3334
const ClientWorldState({
3435
required this.multiplayer,
@@ -48,6 +49,7 @@ final class ClientWorldState extends WorldState with ClientWorldStateMappable {
4849
super.messages,
4950
required super.data,
5051
this.drawerView = DrawerView.chat,
52+
this.zoom = 1.0,
5153
});
5254

5355
QuokkaFileSystem get fileSystem => assetManager.fileSystem;

app/lib/bloc/world/state.mapper.dart

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ class ClientWorldStateMapper extends ClassMapperBase<ClientWorldState> {
172172
static DrawerView _$drawerView(ClientWorldState v) => v.drawerView;
173173
static const Field<ClientWorldState, DrawerView> _f$drawerView =
174174
Field('drawerView', _$drawerView, opt: true, def: DrawerView.chat);
175+
static double _$zoom(ClientWorldState v) => v.zoom;
176+
static const Field<ClientWorldState, double> _f$zoom =
177+
Field('zoom', _$zoom, opt: true, def: 1.0);
175178

176179
@override
177180
final MappableFields<ClientWorldState> fields = const {
@@ -192,6 +195,7 @@ class ClientWorldStateMapper extends ClassMapperBase<ClientWorldState> {
192195
#messages: _f$messages,
193196
#data: _f$data,
194197
#drawerView: _f$drawerView,
198+
#zoom: _f$zoom,
195199
};
196200

197201
static ClientWorldState _instantiate(DecodingData data) {
@@ -212,7 +216,8 @@ class ClientWorldStateMapper extends ClassMapperBase<ClientWorldState> {
212216
teamMembers: data.dec(_f$teamMembers),
213217
messages: data.dec(_f$messages),
214218
data: data.dec(_f$data),
215-
drawerView: data.dec(_f$drawerView));
219+
drawerView: data.dec(_f$drawerView),
220+
zoom: data.dec(_f$zoom));
216221
}
217222

218223
@override
@@ -302,7 +307,8 @@ abstract class ClientWorldStateCopyWith<$R, $In extends ClientWorldState, $Out>
302307
Map<String, Set<int>>? teamMembers,
303308
List<ChatMessage>? messages,
304309
QuokkaData? data,
305-
DrawerView? drawerView});
310+
DrawerView? drawerView,
311+
double? zoom});
306312
ClientWorldStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(
307313
Then<$Out2, $R2> t);
308314
}
@@ -360,7 +366,8 @@ class _ClientWorldStateCopyWithImpl<$R, $Out>
360366
Map<String, Set<int>>? teamMembers,
361367
List<ChatMessage>? messages,
362368
QuokkaData? data,
363-
DrawerView? drawerView}) =>
369+
DrawerView? drawerView,
370+
double? zoom}) =>
364371
$apply(FieldCopyWithData({
365372
if (multiplayer != null) #multiplayer: multiplayer,
366373
if (colorScheme != null) #colorScheme: colorScheme,
@@ -378,7 +385,8 @@ class _ClientWorldStateCopyWithImpl<$R, $Out>
378385
if (teamMembers != null) #teamMembers: teamMembers,
379386
if (messages != null) #messages: messages,
380387
if (data != null) #data: data,
381-
if (drawerView != null) #drawerView: drawerView
388+
if (drawerView != null) #drawerView: drawerView,
389+
if (zoom != null) #zoom: zoom
382390
}));
383391
@override
384392
ClientWorldState $make(CopyWithData data) => ClientWorldState(
@@ -399,7 +407,8 @@ class _ClientWorldStateCopyWithImpl<$R, $Out>
399407
teamMembers: data.get(#teamMembers, or: $value.teamMembers),
400408
messages: data.get(#messages, or: $value.messages),
401409
data: data.get(#data, or: $value.data),
402-
drawerView: data.get(#drawerView, or: $value.drawerView));
410+
drawerView: data.get(#drawerView, or: $value.drawerView),
411+
zoom: data.get(#zoom, or: $value.zoom));
403412

404413
@override
405414
ClientWorldStateCopyWith<$R2, ClientWorldState, $Out2> $chain<$R2, $Out2>(

app/lib/board/cell.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ class GameCell extends PositionComponent
135135
}
136136

137137
VectorDefinition toDefinition() =>
138-
(position.clone()..divide(grid.cellSize)).toDefinition();
138+
(position.clone()..divide(grid.cellSizeWithZoom)).toDefinition();
139139

140140
GlobalVectorDefinition toGlobalDefinition(ClientWorldState state) =>
141141
GlobalVectorDefinition.fromLocal(state.tableName, toDefinition());

app/lib/board/grid.dart

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,29 @@
11
import 'package:flame/components.dart';
22
import 'package:flame/extensions.dart';
3+
import 'package:flame_bloc/flame_bloc.dart';
4+
import 'package:quokka/bloc/world/bloc.dart';
5+
import 'package:quokka/bloc/world/state.dart';
36
import 'package:quokka/board/cell.dart';
47

5-
class BoardGrid extends PositionComponent with HasGameRef {
8+
class BoardGrid extends PositionComponent
9+
with HasGameRef, FlameBlocListenable<WorldBloc, ClientWorldState> {
610
final Vector2 cellSize;
711
static const _padding = 3.0;
812
Rect? _lastViewport;
13+
double _zoom = 1.0;
914

1015
BoardGrid({
1116
required this.cellSize,
1217
});
1318

1419
Rect get viewport {
1520
final Rect viewport = game.camera.visibleWorldRect;
21+
final currentSize = cellSizeWithZoom;
1622
return Rect.fromLTRB(
17-
(viewport.left / cellSize.x - _padding).floor() * cellSize.x,
18-
(viewport.top / cellSize.y - _padding).floor() * cellSize.y,
19-
(viewport.right / cellSize.x + _padding).ceil() * cellSize.x,
20-
(viewport.bottom / cellSize.y + _padding).ceil() * cellSize.y,
23+
(viewport.left / currentSize.x - _padding).floor() * currentSize.x,
24+
(viewport.top / currentSize.y - _padding).floor() * currentSize.y,
25+
(viewport.right / currentSize.x + _padding).ceil() * currentSize.x,
26+
(viewport.bottom / currentSize.y + _padding).ceil() * currentSize.y,
2127
);
2228
}
2329

@@ -32,6 +38,7 @@ class BoardGrid extends PositionComponent with HasGameRef {
3238
void _updateGrid() {
3339
if (!shouldReset()) return;
3440
final viewport = this.viewport;
41+
final currentSize = cellSizeWithZoom;
3542
// Remove components that are out of the viewport
3643
removeAll(children.where((element) {
3744
if (element is! PositionComponent) return false;
@@ -41,38 +48,40 @@ class BoardGrid extends PositionComponent with HasGameRef {
4148
final last = _lastViewport ?? Rect.zero;
4249
// Add components that are in the viewport
4350
// Top and bottom
44-
for (var x = viewport.left; x < viewport.right; x += cellSize.x) {
45-
for (var y = viewport.top; y < last.top; y += cellSize.y) {
51+
for (var x = viewport.left; x < viewport.right; x += currentSize.x) {
52+
for (var y = viewport.top; y < last.top; y += currentSize.y) {
4653
add(_createCell(
4754
position: Vector2(x, y),
48-
size: cellSize,
55+
size: currentSize,
4956
));
5057
}
51-
for (var y = last.bottom; y < viewport.bottom; y += cellSize.y) {
58+
for (var y = last.bottom; y < viewport.bottom; y += currentSize.y) {
5259
add(_createCell(
5360
position: Vector2(x, y),
54-
size: cellSize,
61+
size: currentSize,
5562
));
5663
}
5764
}
5865
// Left and right
59-
for (var y = last.top; y < last.bottom; y += cellSize.y) {
60-
for (var x = viewport.left; x < last.left; x += cellSize.x) {
66+
for (var y = last.top; y < last.bottom; y += currentSize.y) {
67+
for (var x = viewport.left; x < last.left; x += currentSize.x) {
6168
add(_createCell(
6269
position: Vector2(x, y),
63-
size: cellSize,
70+
size: currentSize,
6471
));
6572
}
66-
for (var x = last.right; x < viewport.right; x += cellSize.x) {
73+
for (var x = last.right; x < viewport.right; x += currentSize.x) {
6774
add(_createCell(
6875
position: Vector2(x, y),
69-
size: cellSize,
76+
size: currentSize,
7077
));
7178
}
7279
}
7380
_lastViewport = viewport;
7481
}
7582

83+
Vector2 get cellSizeWithZoom => cellSize * _zoom;
84+
7685
@override
7786
void update(double dt) {
7887
super.update(dt);
@@ -90,4 +99,22 @@ class BoardGrid extends PositionComponent with HasGameRef {
9099
position: position,
91100
size: size,
92101
);
102+
103+
@override
104+
void onInitialState(ClientWorldState state) {
105+
_zoom = state.zoom;
106+
}
107+
108+
@override
109+
bool listenWhen(ClientWorldState previousState, ClientWorldState newState) =>
110+
previousState.zoom != newState.zoom;
111+
112+
@override
113+
void onNewState(ClientWorldState state) {
114+
if (_zoom != state.zoom) {
115+
_zoom = state.zoom;
116+
_lastViewport = null;
117+
removeAll(children);
118+
}
119+
}
93120
}

app/lib/helpers/vector.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import 'package:flame/components.dart';
22
import 'package:quokka_api/quokka_api.dart';
33

44
extension VectorToDefinition on Vector2 {
5-
VectorDefinition toDefinition() => VectorDefinition(x.toInt(), y.toInt());
5+
VectorDefinition toDefinition() => VectorDefinition(x.round(), y.round());
66
}
77

88
extension DefinitionToVector on VectorDefinition {

app/lib/l10n/app_en.arb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,5 +180,9 @@
180180
"highContrast": "High contrast",
181181
"noServers": "There are no servers available",
182182
"expand": "Expand",
183-
"collapse": "Collapse"
183+
"collapse": "Collapse",
184+
"zoom": "Zoom",
185+
"zoomIn": "Zoom in",
186+
"zoomOut": "Zoom out",
187+
"resetZoom": "Reset zoom"
184188
}

0 commit comments

Comments
 (0)