Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added keyboard controls for map gestures #1987

Merged
merged 28 commits into from
Feb 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3ee9d79
Added keyboard controls for map gestures
JaffaKetchup Nov 19, 2024
83a29cd
Fixed linting issues
JaffaKetchup Nov 19, 2024
e4a59b9
Switched to using physical keys to support other keyboard models
JaffaKetchup Nov 19, 2024
4c1f201
Fixed linting issue
JaffaKetchup Nov 19, 2024
1ecb940
Removed unnecessary guard clauses
JaffaKetchup Nov 19, 2024
805f882
Removed duplicate 'dart:math' import
JaffaKetchup Nov 19, 2024
507eaa4
Merge branch 'master' into arrow-keys
JaffaKetchup Nov 25, 2024
3c9e0d5
Improved interactive flags example page
JaffaKetchup Nov 26, 2024
3b29a4e
Merge branch 'master' into arrow-keys
JaffaKetchup Dec 5, 2024
343e9a9
Merge branch 'master' into arrow-keys
JaffaKetchup Dec 5, 2024
5e52ef7
Update to use `Offset`s
JaffaKetchup Dec 5, 2024
3615a92
Merge branch 'master' into arrow-keys
JaffaKetchup Jan 5, 2025
eb560ca
Buttery smooth: use animations instead of tying controller to key hol…
JaffaKetchup Jan 12, 2025
41d15fe
Respect updates to `KeyboardOptions`
JaffaKetchup Jan 12, 2025
e0487e5
Merge branch 'master' into arrow-keys
JaffaKetchup Jan 12, 2025
085a7ae
Minor scope change
JaffaKetchup Jan 12, 2025
96d0b24
Adjusted defaults
JaffaKetchup Jan 12, 2025
d099d0d
Improved implementation (refactoring etc. to reduce duplication)
JaffaKetchup Jan 13, 2025
3b38701
Minor renaming
JaffaKetchup Jan 13, 2025
de24a5d
Merge branch 'master' into arrow-keys
JaffaKetchup Jan 14, 2025
8d68eaf
Merge branch 'master' into arrow-keys
JaffaKetchup Jan 14, 2025
6831587
Minor improvements
JaffaKetchup Jan 20, 2025
d14280e
Fixed issue where diagonal movement was still faster than axis-aligne…
JaffaKetchup Jan 28, 2025
29bb8b3
Improved leaping support
JaffaKetchup Jan 28, 2025
224cac1
Added `leapMaxOfCurveComponent`
JaffaKetchup Jan 28, 2025
6526fc1
Changed defaults
JaffaKetchup Jan 28, 2025
e4b28e0
Merge branch 'master' into arrow-keys
JaffaKetchup Jan 30, 2025
e5eae2f
Merge branch 'master' into arrow-keys
JaffaKetchup Jan 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
308 changes: 225 additions & 83 deletions example/lib/pages/interactive_test_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,16 @@ class InteractiveFlagsPage extends StatefulWidget {
}

class _InteractiveFlagsPageState extends State<InteractiveFlagsPage> {
static const availableFlags = {
'Movement': {
InteractiveFlag.drag: 'Drag',
InteractiveFlag.flingAnimation: 'Fling',
InteractiveFlag.pinchMove: 'Pinch',
},
'Zooming': {
InteractiveFlag.pinchZoom: 'Pinch',
InteractiveFlag.scrollWheelZoom: 'Scroll',
InteractiveFlag.doubleTapZoom: 'Double tap',
InteractiveFlag.doubleTapDragZoom: '+ drag',
},
'Rotation': {
InteractiveFlag.rotate: 'Twist',
},
};
final flagsSet =
ValueNotifier(InteractiveFlag.drag | InteractiveFlag.pinchZoom);

int flags = InteractiveFlag.drag | InteractiveFlag.pinchZoom;
bool keyboardCursorRotate = false;
bool keyboardArrowsMove = false;
bool keyboardWASDMove = false;
bool keyboardQERotate = false;
bool keyboardRFZoom = false;

MapEvent? _latestEvent;

@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.sizeOf(context).width;
Expand All @@ -50,58 +38,181 @@ class _InteractiveFlagsPageState extends State<InteractiveFlagsPage> {
direction: screenWidth >= 600 ? Axis.horizontal : Axis.vertical,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: availableFlags.entries
.map<Widget?>(
(category) => Column(
children: [
Column(
children: [
const Text(
'Move/Pan',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 6),
Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
category.key,
style: const TextStyle(fontWeight: FontWeight.bold),
InteractiveFlagCheckbox(
name: 'Drag',
flag: InteractiveFlag.drag,
flagsSet: flagsSet,
),
const SizedBox(width: 8),
InteractiveFlagCheckbox(
name: 'Fling',
flag: InteractiveFlag.flingAnimation,
flagsSet: flagsSet,
),
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
...category.value.entries.map(
(e) => Column(
children: [
Checkbox.adaptive(
value:
InteractiveFlag.hasFlag(e.key, flags),
onChanged: (enabled) {
if (!enabled!) {
setState(() => flags &= ~e.key);
return;
}
setState(() => flags |= e.key);
},
),
Text(e.value),
],
const SizedBox(width: 8),
InteractiveFlagCheckbox(
name: 'Pinch',
flag: InteractiveFlag.pinchMove,
flagsSet: flagsSet,
),
const SizedBox(width: 8),
Column(
children: [
Checkbox.adaptive(
value: keyboardArrowsMove,
onChanged: (enabled) => setState(
() => keyboardArrowsMove = enabled!,
),
),
if (category.key == 'Rotation') ...[
Column(
children: [
Checkbox.adaptive(
value: keyboardCursorRotate,
onChanged: (enabled) => setState(
() => keyboardCursorRotate = enabled!),
),
const Text('Cursor & CTRL'),
],
const Text(
'Keyboard\nArrows',
textAlign: TextAlign.center,
),
],
),
const SizedBox(width: 8),
Column(
children: [
Checkbox.adaptive(
value: keyboardWASDMove,
onChanged: (enabled) => setState(
() => keyboardWASDMove = enabled!,
),
]
].interleave(const SizedBox(width: 12)).toList()
..removeLast(),
)
),
const Text(
'Keyboard\nW/A/S/D',
textAlign: TextAlign.center,
),
],
),
],
)
],
),
const SizedBox(width: 12),
Column(
children: [
const Text(
'Zoom',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
)
.interleave(
screenWidth >= 600 ? null : const SizedBox(height: 12),
)
.whereType<Widget>()
.toList(),
const SizedBox(height: 6),
Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InteractiveFlagCheckbox(
name: 'Pinch',
flag: InteractiveFlag.pinchZoom,
flagsSet: flagsSet,
),
const SizedBox(width: 8),
InteractiveFlagCheckbox(
name: 'Scroll',
flag: InteractiveFlag.scrollWheelZoom,
flagsSet: flagsSet,
),
const SizedBox(width: 8),
InteractiveFlagCheckbox(
name: 'Double tap',
flag: InteractiveFlag.doubleTapZoom,
flagsSet: flagsSet,
),
const SizedBox(width: 8),
InteractiveFlagCheckbox(
name: '+ drag',
flag: InteractiveFlag.doubleTapDragZoom,
flagsSet: flagsSet,
),
const SizedBox(width: 8),
Column(
children: [
Checkbox.adaptive(
value: keyboardRFZoom,
onChanged: (enabled) => setState(
() => keyboardRFZoom = enabled!,
),
),
const Text(
'Keyboard\nR/F',
textAlign: TextAlign.center,
),
],
),
],
)
],
),
const SizedBox(width: 12),
Column(
children: [
const Text(
'Rotate',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 6),
Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InteractiveFlagCheckbox(
name: 'Twist',
flag: InteractiveFlag.rotate,
flagsSet: flagsSet,
),
const SizedBox(width: 8),
Column(
children: [
Checkbox.adaptive(
value: keyboardCursorRotate,
onChanged: (enabled) => setState(
() => keyboardCursorRotate = enabled!,
),
),
const Text(
'Cursor\n& CTRL',
textAlign: TextAlign.center,
),
],
),
const SizedBox(width: 8),
Column(
children: [
Checkbox.adaptive(
value: keyboardQERotate,
onChanged: (enabled) => setState(
() => keyboardQERotate = enabled!,
),
),
const Text(
'Keyboard\nQ/E',
textAlign: TextAlign.center,
),
],
),
],
)
],
),
],
),
const Divider(),
Padding(
Expand All @@ -115,23 +226,33 @@ class _InteractiveFlagsPageState extends State<InteractiveFlagsPage> {
),
),
Expanded(
child: FlutterMap(
options: MapOptions(
onMapEvent: (evt) => setState(() => _latestEvent = evt),
initialCenter: const LatLng(51.5, -0.09),
initialZoom: 11,
interactionOptions: InteractionOptions(
flags: flags,
cursorKeyboardRotationOptions:
CursorKeyboardRotationOptions(
isKeyTrigger: (key) =>
keyboardCursorRotate &&
CursorKeyboardRotationOptions.defaultTriggerKeys
.contains(key),
child: ValueListenableBuilder(
valueListenable: flagsSet,
builder: (context, value, child) => FlutterMap(
options: MapOptions(
onMapEvent: (evt) => setState(() => _latestEvent = evt),
initialCenter: const LatLng(51.5, -0.09),
initialZoom: 11,
interactionOptions: InteractionOptions(
flags: value,
cursorKeyboardRotationOptions:
CursorKeyboardRotationOptions(
isKeyTrigger: (key) =>
keyboardCursorRotate &&
CursorKeyboardRotationOptions.defaultTriggerKeys
.contains(key),
),
keyboardOptions: KeyboardOptions(
enableArrowKeysPanning: keyboardArrowsMove,
enableWASDPanning: keyboardWASDMove,
enableQERotating: keyboardQERotate,
enableRFZooming: keyboardRFZoom,
),
),
),
children: [child!],
),
children: [openStreetMapTileLayer],
child: openStreetMapTileLayer,
),
),
],
Expand Down Expand Up @@ -186,11 +307,32 @@ class _InteractiveFlagsPageState extends State<InteractiveFlagsPage> {
}
}

extension _IterableExt<E> on Iterable<E> {
Iterable<E> interleave(E separator) sync* {
for (int i = 0; i < length; i++) {
yield elementAt(i);
if (i < length) yield separator;
}
class InteractiveFlagCheckbox extends StatelessWidget {
const InteractiveFlagCheckbox({
super.key,
required this.name,
required this.flag,
required this.flagsSet,
});

final String name;
final int flag;
final ValueNotifier<int> flagsSet;

@override
Widget build(BuildContext context) {
return Column(
children: [
ValueListenableBuilder(
valueListenable: flagsSet,
builder: (context, value, _) => Checkbox.adaptive(
value: InteractiveFlag.hasFlag(flag, value),
onChanged: (enabled) =>
flagsSet.value = !enabled! ? value &= ~flag : value |= flag,
),
),
Text(name),
],
);
}
}
1 change: 1 addition & 0 deletions lib/flutter_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,6 @@ export 'package:flutter_map/src/map/controller/map_controller.dart';
export 'package:flutter_map/src/map/controller/map_controller_impl.dart';
export 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart';
export 'package:flutter_map/src/map/options/interaction.dart';
export 'package:flutter_map/src/map/options/keyboard.dart';
export 'package:flutter_map/src/map/options/options.dart';
export 'package:flutter_map/src/map/widget.dart';
Loading