Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
00d9f8df
Unverified
Commit
00d9f8df
authored
Jul 09, 2021
by
Greg Spencer
Committed by
GitHub
Jul 09, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add CallbackShortcuts widget (#86045)
parent
cb17425d
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
256 additions
and
0 deletions
+256
-0
shortcuts.dart
packages/flutter/lib/src/widgets/shortcuts.dart
+89
-0
shortcuts_test.dart
packages/flutter/test/widgets/shortcuts_test.dart
+167
-0
No files found.
packages/flutter/lib/src/widgets/shortcuts.dart
View file @
00d9f8df
...
@@ -1026,6 +1026,8 @@ class ShortcutManager extends ChangeNotifier with Diagnosticable {
...
@@ -1026,6 +1026,8 @@ class ShortcutManager extends ChangeNotifier with Diagnosticable {
///
///
/// See also:
/// See also:
///
///
/// * [CallbackShortcuts], a less complicated (but less flexible) way of
/// defining key bindings that just invoke callbacks.
/// * [Intent], a class for containing a description of a user action to be
/// * [Intent], a class for containing a description of a user action to be
/// invoked.
/// invoked.
/// * [Action], a class for defining an invocation of a user action.
/// * [Action], a class for defining an invocation of a user action.
...
@@ -1197,3 +1199,90 @@ class _ShortcutsMarker extends InheritedNotifier<ShortcutManager> {
...
@@ -1197,3 +1199,90 @@ class _ShortcutsMarker extends InheritedNotifier<ShortcutManager> {
ShortcutManager
get
manager
=>
super
.
notifier
!;
ShortcutManager
get
manager
=>
super
.
notifier
!;
}
}
/// A widget that provides an uncomplicated mechanism for binding a key
/// combination to a specific callback.
///
/// This is similar to the functionality provided by the [Shortcuts] widget, but
/// instead of requiring a mapping to an [Intent], and an [Actions] widget
/// somewhere in the widget tree to bind the [Intent] to, it just takes a set of
/// bindings that bind the key combination directly to a [VoidCallback].
///
/// Because it is a simpler mechanism, it doesn't provide the ability to disable
/// the callbacks, or to separate the definition of the shortcuts from the
/// definition of the code that is triggered by them (the role that actions play
/// in the [Shortcuts]/[Actions] system).
///
/// However, for some applications the complexity and flexibility of the
/// [Shortcuts] and [Actions] mechanism is overkill, and this widget is here for
/// those apps.
///
/// [Shortcuts] and [CallbackShortcuts] can both be used in the same app. As
/// with any key handling widget, if this widget handles a key event then
/// widgets above it in the focus chain will not receive the event. This means
/// that if this widget handles a key, then an ancestor [Shortcuts] widget (or
/// any other key handling widget) will not receive that key, and similarly, if
/// a descendant of this widget handles the key, then the key event will not
/// reach this widget for handling.
///
/// See also:
/// * [Focus], a widget that defines which widgets can receive keyboard focus.
class
CallbackShortcuts
extends
StatelessWidget
{
/// Creates a const [CallbackShortcuts] widget.
const
CallbackShortcuts
({
Key
?
key
,
required
this
.
bindings
,
required
this
.
child
,
})
:
super
(
key:
key
);
/// A map of key combinations to callbacks used to define the shortcut
/// bindings.
///
/// If a descendant of this widget has focus, and a key is pressed, the
/// activator keys of this map will be asked if they accept the key event. If
/// they do, then the corresponding callback is invoked, and the key event
/// propagation is halted. If none of the activators accept the key event,
/// then the key event continues to be propagated up the focus chain.
///
/// If more than one activator accepts the key event, then all of the
/// callbacks associated with activators that accept the key event are
/// invoked.
///
/// Some examples of [ShortcutActivator] subclasses that can be used to define
/// the key combinations here are [SingleActivator], [CharacterActivator], and
/// [LogicalKeySet].
final
Map
<
ShortcutActivator
,
VoidCallback
>
bindings
;
/// The widget below this widget in the tree.
///
/// {@macro flutter.widgets.ProxyWidget.child}
final
Widget
child
;
// A helper function to make the stack trace more useful if the callback
// throws, by providing the activator and event as arguments that will appear
// in the stack trace.
bool
_applyKeyBinding
(
ShortcutActivator
activator
,
RawKeyEvent
event
)
{
if
(
activator
.
accepts
(
event
,
RawKeyboard
.
instance
))
{
bindings
[
activator
]!.
call
();
return
true
;
}
return
false
;
}
@override
Widget
build
(
BuildContext
context
)
{
return
Focus
(
canRequestFocus:
false
,
skipTraversal:
true
,
onKey:
(
FocusNode
node
,
RawKeyEvent
event
)
{
KeyEventResult
result
=
KeyEventResult
.
ignored
;
// Activates all key bindings that match, returns "handled" if any handle it.
for
(
final
ShortcutActivator
activator
in
bindings
.
keys
)
{
result
=
_applyKeyBinding
(
activator
,
event
)
?
KeyEventResult
.
handled
:
result
;
}
return
result
;
},
child:
child
,
);
}
}
\ No newline at end of file
packages/flutter/test/widgets/shortcuts_test.dart
View file @
00d9f8df
...
@@ -1077,4 +1077,171 @@ void main() {
...
@@ -1077,4 +1077,171 @@ void main() {
invoked
=
0
;
invoked
=
0
;
});
});
});
});
group
(
'CallbackShortcuts'
,
()
{
testWidgets
(
'trigger on key events'
,
(
WidgetTester
tester
)
async
{
int
invoked
=
0
;
await
tester
.
pumpWidget
(
CallbackShortcuts
(
bindings:
<
ShortcutActivator
,
VoidCallback
>{
const
SingleActivator
(
LogicalKeyboardKey
.
keyA
):
()
{
invoked
+=
1
;
},
},
child:
const
Focus
(
autofocus:
true
,
child:
Placeholder
(),
),
),
);
await
tester
.
pump
();
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
keyA
);
expect
(
invoked
,
equals
(
1
));
await
tester
.
sendKeyUpEvent
(
LogicalKeyboardKey
.
keyA
);
expect
(
invoked
,
equals
(
1
));
});
testWidgets
(
'nested CallbackShortcuts stop propagation'
,
(
WidgetTester
tester
)
async
{
int
invokedOuter
=
0
;
int
invokedInner
=
0
;
await
tester
.
pumpWidget
(
CallbackShortcuts
(
bindings:
<
ShortcutActivator
,
VoidCallback
>{
const
SingleActivator
(
LogicalKeyboardKey
.
keyA
):
()
{
invokedOuter
+=
1
;
},
},
child:
CallbackShortcuts
(
bindings:
<
ShortcutActivator
,
VoidCallback
>{
const
SingleActivator
(
LogicalKeyboardKey
.
keyA
):
()
{
invokedInner
+=
1
;
},
},
child:
const
Focus
(
autofocus:
true
,
child:
Placeholder
(),
),
),
),
);
await
tester
.
pump
();
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
keyA
);
expect
(
invokedOuter
,
equals
(
0
));
expect
(
invokedInner
,
equals
(
1
));
await
tester
.
sendKeyUpEvent
(
LogicalKeyboardKey
.
keyA
);
expect
(
invokedOuter
,
equals
(
0
));
expect
(
invokedInner
,
equals
(
1
));
});
testWidgets
(
'non-overlapping nested CallbackShortcuts fire appropriately'
,
(
WidgetTester
tester
)
async
{
int
invokedOuter
=
0
;
int
invokedInner
=
0
;
await
tester
.
pumpWidget
(
CallbackShortcuts
(
bindings:
<
ShortcutActivator
,
VoidCallback
>{
const
CharacterActivator
(
'b'
):
()
{
invokedOuter
+=
1
;
},
},
child:
CallbackShortcuts
(
bindings:
<
ShortcutActivator
,
VoidCallback
>{
const
CharacterActivator
(
'a'
):
()
{
invokedInner
+=
1
;
},
},
child:
const
Focus
(
autofocus:
true
,
child:
Placeholder
(),
),
),
),
);
await
tester
.
pump
();
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
keyA
);
expect
(
invokedOuter
,
equals
(
0
));
expect
(
invokedInner
,
equals
(
1
));
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
keyB
);
expect
(
invokedOuter
,
equals
(
1
));
expect
(
invokedInner
,
equals
(
1
));
await
tester
.
sendKeyUpEvent
(
LogicalKeyboardKey
.
keyA
);
await
tester
.
sendKeyUpEvent
(
LogicalKeyboardKey
.
keyB
);
expect
(
invokedOuter
,
equals
(
1
));
expect
(
invokedInner
,
equals
(
1
));
});
testWidgets
(
'Works correctly with Shortcuts too'
,
(
WidgetTester
tester
)
async
{
int
invokedCallbackA
=
0
;
int
invokedCallbackB
=
0
;
int
invokedActionA
=
0
;
int
invokedActionB
=
0
;
void
clear
()
{
invokedCallbackA
=
0
;
invokedCallbackB
=
0
;
invokedActionA
=
0
;
invokedActionB
=
0
;
}
await
tester
.
pumpWidget
(
Actions
(
actions:
<
Type
,
Action
<
Intent
>>{
TestIntent:
TestAction
(
onInvoke:
(
Intent
intent
)
{
invokedActionA
+=
1
;
return
true
;
},
),
TestIntent2:
TestAction
(
onInvoke:
(
Intent
intent
)
{
invokedActionB
+=
1
;
return
true
;
},
),
},
child:
CallbackShortcuts
(
bindings:
<
ShortcutActivator
,
VoidCallback
>{
const
CharacterActivator
(
'b'
):
()
{
invokedCallbackB
+=
1
;
},
},
child:
Shortcuts
(
shortcuts:
<
LogicalKeySet
,
Intent
>{
LogicalKeySet
(
LogicalKeyboardKey
.
keyA
):
const
TestIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
keyB
):
const
TestIntent2
(),
},
child:
CallbackShortcuts
(
bindings:
<
ShortcutActivator
,
VoidCallback
>{
const
CharacterActivator
(
'a'
):
()
{
invokedCallbackA
+=
1
;
},
},
child:
const
Focus
(
autofocus:
true
,
child:
Placeholder
(),
),
),
),
),
),
);
await
tester
.
pump
();
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
keyA
);
expect
(
invokedCallbackA
,
equals
(
1
));
expect
(
invokedCallbackB
,
equals
(
0
));
expect
(
invokedActionA
,
equals
(
0
));
expect
(
invokedActionB
,
equals
(
0
));
await
tester
.
sendKeyUpEvent
(
LogicalKeyboardKey
.
keyA
);
clear
();
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
keyB
);
expect
(
invokedCallbackA
,
equals
(
0
));
expect
(
invokedCallbackB
,
equals
(
0
));
expect
(
invokedActionA
,
equals
(
0
));
expect
(
invokedActionB
,
equals
(
1
));
await
tester
.
sendKeyUpEvent
(
LogicalKeyboardKey
.
keyB
);
});
});
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment