Skip to content

Commit 89efe2c

Browse files
committed
widgets can take focusable and a onFocusSelect
1 parent 4a17bca commit 89efe2c

File tree

3 files changed

+65
-5
lines changed

3 files changed

+65
-5
lines changed

lib/ensemble_app.dart

+7-5
Original file line numberDiff line numberDiff line change
@@ -222,11 +222,13 @@ class EnsembleAppState extends State<EnsembleApp> with WidgetsBindingObserver {
222222
// The Page's Scaffold can handle the resizing.
223223
resizeToAvoidBottomInset: false,
224224

225-
body: Screen(
226-
appProvider:
227-
AppProvider(definitionProvider: config.definitionProvider),
228-
screenPayload: widget.screenPayload,
229-
),
225+
body: FocusTraversalGroup(
226+
policy: ReadingOrderTraversalPolicy(),
227+
child: Screen(
228+
appProvider:
229+
AppProvider(definitionProvider: config.definitionProvider),
230+
screenPayload: widget.screenPayload,
231+
)),
230232
),
231233
useInheritedMediaQuery: widget.isPreview,
232234
locale: widget.isPreview ? DevicePreview.locale(context) : null,

lib/framework/widget/widget.dart

+50
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ import 'package:ensemble/framework/view/page_group.dart';
88
import 'package:ensemble/framework/widget/icon.dart' as ensemble;
99
import 'package:ensemble/framework/view/page.dart';
1010
import 'package:ensemble/page_model.dart';
11+
import 'package:ensemble/screen_controller.dart';
1112
import 'package:ensemble/util/utils.dart';
1213
import 'package:ensemble/widget/helpers/controllers.dart';
1314
import 'package:ensemble_ts_interpreter/invokables/invokable.dart';
1415
import 'package:flutter/material.dart';
16+
import 'package:flutter/services.dart';
1517
import 'package:pointer_interceptor/pointer_interceptor.dart';
1618
import 'package:yaml/yaml.dart';
1719

@@ -24,6 +26,8 @@ mixin UpdatableContainer<T extends Widget> {
2426
/// base class for widgets that want to participate in Ensemble layout
2527
abstract class WidgetState<W extends HasController> extends BaseWidgetState<W> {
2628
ScopeManager? scopeManager;
29+
FocusNode? widgetFocusNode;
30+
bool hasFocus = false;
2731

2832
@override
2933
Widget build(BuildContext context) {
@@ -87,14 +91,59 @@ abstract class WidgetState<W extends HasController> extends BaseWidgetState<W> {
8791
/// So if we put Expanded on the Column's child, layout exception will occur
8892
rtn = Expanded(child: rtn);
8993
}
94+
95+
if (widgetController.focusable && widgetFocusNode != null) {
96+
rtn = Focus(
97+
focusNode: widgetFocusNode,
98+
onKey: (node, event) => _onFocusByKey(node, event, widgetController),
99+
child: Container(
100+
decoration: BoxDecoration(
101+
borderRadius: widgetController is BoxController
102+
? widgetController.borderRadius?.getValue()
103+
: null,
104+
border: Border.all(
105+
color: hasFocus ? Colors.white : Colors.transparent,
106+
width: 1)),
107+
child: ClipRRect(clipBehavior: Clip.hardEdge, child: rtn)));
108+
}
109+
90110
return rtn;
91111
}
92112
throw LanguageError("Wrong usage of widget controller!");
93113
}
94114

115+
KeyEventResult _onFocusByKey(FocusNode node, RawKeyEvent event, WidgetController controller) {
116+
if (controller.onFocusSelect != null && event is RawKeyUpEvent && event.logicalKey == LogicalKeyboardKey.select) {
117+
ScreenController().executeAction(context, controller.onFocusSelect!);
118+
return KeyEventResult.handled;
119+
}
120+
return KeyEventResult.ignored;
121+
}
122+
95123
/// build your widget here
96124
Widget buildWidget(BuildContext context);
97125

126+
@override
127+
void initState() {
128+
super.initState();
129+
if (widget.controller is WidgetController &&
130+
(widget.controller as WidgetController).focusable) {
131+
widgetFocusNode = _initFocus();
132+
}
133+
}
134+
135+
FocusNode _initFocus() {
136+
var myFocusNode = FocusNode();
137+
myFocusNode.addListener(() {
138+
if (myFocusNode.hasFocus != hasFocus) {
139+
setState(() {
140+
hasFocus = myFocusNode.hasFocus;
141+
});
142+
}
143+
});
144+
return myFocusNode;
145+
}
146+
98147
@override
99148
void didChangeDependencies() {
100149
super.didChangeDependencies();
@@ -107,6 +156,7 @@ abstract class WidgetState<W extends HasController> extends BaseWidgetState<W> {
107156
if (widget is Invokable) {
108157
scopeManager?.removeBindingListeners(widget as Invokable);
109158
}
159+
widgetFocusNode?.dispose();
110160
super.dispose();
111161
}
112162

lib/widget/helpers/controllers.dart

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/// This class contains helper controllers for our widgets.
22
import 'package:ensemble/controller/controller_mixins.dart';
3+
import 'package:ensemble/framework/action.dart';
34
import 'package:ensemble/framework/extensions.dart';
45
import 'package:ensemble/framework/model.dart';
56
import 'package:ensemble/util/utils.dart';
@@ -116,6 +117,9 @@ abstract class WidgetController extends Controller {
116117
// base properties applicable to all widgets
117118
bool expanded = false;
118119

120+
bool focusable = false;
121+
EnsembleAction? onFocusSelect;
122+
119123
bool visible = true;
120124
Duration? visibilityTransitionDuration; // in seconds
121125

@@ -153,6 +157,10 @@ abstract class WidgetController extends Controller {
153157
Map<String, Function> getBaseSetters() {
154158
return {
155159
'expanded': (value) => expanded = Utils.getBool(value, fallback: false),
160+
'focusable': (value) =>
161+
focusable = Utils.getBool(value, fallback: focusable),
162+
'onFocusSelect': (value) =>
163+
onFocusSelect = EnsembleAction.fromYaml(value),
156164
'visible': (value) => visible = Utils.getBool(value, fallback: true),
157165
'visibilityTransitionDuration': (value) =>
158166
visibilityTransitionDuration = Utils.getDuration(value),

0 commit comments

Comments
 (0)