|
@@ -10,6 +10,143 @@ import 'image.dart';
|
10
|
10
|
import 'theme.dart';
|
11
|
11
|
import 'toolbar.dart';
|
12
|
12
|
|
|
13
|
+class ZefyrEditorScope extends ChangeNotifier {
|
|
14
|
+ ZefyrEditorScope({
|
|
15
|
+ @required ZefyrImageDelegate imageDelegate,
|
|
16
|
+ @required ZefyrController controller,
|
|
17
|
+ @required FocusNode focusNode,
|
|
18
|
+ @required FocusNode toolbarFocusNode,
|
|
19
|
+ }) : _controller = controller,
|
|
20
|
+ _imageDelegate = imageDelegate,
|
|
21
|
+ _focusNode = focusNode,
|
|
22
|
+ _toolbarFocusNode = toolbarFocusNode {
|
|
23
|
+ _selectionStyle = _controller.getSelectionStyle();
|
|
24
|
+ _selection = _controller.selection;
|
|
25
|
+ _controller.addListener(_handleControllerChange);
|
|
26
|
+ toolbarFocusNode.addListener(_handleFocusChange);
|
|
27
|
+ _focusNode.addListener(_handleFocusChange);
|
|
28
|
+ }
|
|
29
|
+
|
|
30
|
+ bool _disposed = false;
|
|
31
|
+
|
|
32
|
+ ZefyrImageDelegate _imageDelegate;
|
|
33
|
+ ZefyrImageDelegate get imageDelegate => _imageDelegate;
|
|
34
|
+
|
|
35
|
+ FocusNode _focusNode;
|
|
36
|
+ FocusNode _toolbarFocusNode;
|
|
37
|
+ FocusNode get toolbarFocusNode => _toolbarFocusNode;
|
|
38
|
+
|
|
39
|
+ ZefyrController _controller;
|
|
40
|
+ NotusStyle get selectionStyle => _selectionStyle;
|
|
41
|
+ NotusStyle _selectionStyle;
|
|
42
|
+ TextSelection get selection => _selection;
|
|
43
|
+ TextSelection _selection;
|
|
44
|
+
|
|
45
|
+ @override
|
|
46
|
+ void dispose() {
|
|
47
|
+ assert(!_disposed);
|
|
48
|
+ _controller.removeListener(_handleControllerChange);
|
|
49
|
+ _toolbarFocusNode.removeListener(_handleFocusChange);
|
|
50
|
+ _focusNode.removeListener(_handleFocusChange);
|
|
51
|
+ _disposed = true;
|
|
52
|
+ super.dispose();
|
|
53
|
+ }
|
|
54
|
+
|
|
55
|
+ void _updateControllerIfNeeded(ZefyrController value) {
|
|
56
|
+ if (_controller != value) {
|
|
57
|
+ _controller.removeListener(_handleControllerChange);
|
|
58
|
+ _controller = value;
|
|
59
|
+ _selectionStyle = _controller.getSelectionStyle();
|
|
60
|
+ _selection = _controller.selection;
|
|
61
|
+ _controller.addListener(_handleControllerChange);
|
|
62
|
+ notifyListeners();
|
|
63
|
+ }
|
|
64
|
+ }
|
|
65
|
+
|
|
66
|
+ void _updateFocusNodeIfNeeded(FocusNode value) {
|
|
67
|
+ if (_focusNode != value) {
|
|
68
|
+ _focusNode.removeListener(_handleFocusChange);
|
|
69
|
+ _focusNode = value;
|
|
70
|
+ _focusNode.addListener(_handleFocusChange);
|
|
71
|
+ notifyListeners();
|
|
72
|
+ }
|
|
73
|
+ }
|
|
74
|
+
|
|
75
|
+ void _updateImageDelegateIfNeeded(ZefyrImageDelegate value) {
|
|
76
|
+ if (_imageDelegate != value) {
|
|
77
|
+ _imageDelegate = value;
|
|
78
|
+ notifyListeners();
|
|
79
|
+ }
|
|
80
|
+ }
|
|
81
|
+
|
|
82
|
+ void _handleControllerChange() {
|
|
83
|
+ assert(!_disposed);
|
|
84
|
+ final attrs = _controller.getSelectionStyle();
|
|
85
|
+ final selection = _controller.selection;
|
|
86
|
+ if (_selectionStyle != attrs || _selection != selection) {
|
|
87
|
+ _selectionStyle = attrs;
|
|
88
|
+ _selection = _controller.selection;
|
|
89
|
+ notifyListeners();
|
|
90
|
+ }
|
|
91
|
+ }
|
|
92
|
+
|
|
93
|
+ void _handleFocusChange() {
|
|
94
|
+ assert(!_disposed);
|
|
95
|
+ if (focusOwner == FocusOwner.none && !_selection.isCollapsed) {
|
|
96
|
+ // Collapse selection if there is nothing focused.
|
|
97
|
+ _controller.updateSelection(_selection.copyWith(
|
|
98
|
+ baseOffset: _selection.extentOffset,
|
|
99
|
+ extentOffset: _selection.extentOffset,
|
|
100
|
+ ));
|
|
101
|
+ }
|
|
102
|
+ notifyListeners();
|
|
103
|
+ }
|
|
104
|
+
|
|
105
|
+ FocusOwner get focusOwner {
|
|
106
|
+ assert(!_disposed);
|
|
107
|
+ if (_focusNode.hasFocus) {
|
|
108
|
+ return FocusOwner.editor;
|
|
109
|
+ } else if (toolbarFocusNode.hasFocus) {
|
|
110
|
+ return FocusOwner.toolbar;
|
|
111
|
+ } else {
|
|
112
|
+ return FocusOwner.none;
|
|
113
|
+ }
|
|
114
|
+ }
|
|
115
|
+
|
|
116
|
+ void updateSelection(TextSelection value,
|
|
117
|
+ {ChangeSource source: ChangeSource.remote}) {
|
|
118
|
+ assert(!_disposed);
|
|
119
|
+ _controller.updateSelection(value, source: source);
|
|
120
|
+ }
|
|
121
|
+
|
|
122
|
+ void formatSelection(NotusAttribute value) {
|
|
123
|
+ assert(!_disposed);
|
|
124
|
+ _controller.formatSelection(value);
|
|
125
|
+ }
|
|
126
|
+
|
|
127
|
+ void focus(BuildContext context) {
|
|
128
|
+ assert(!_disposed);
|
|
129
|
+ FocusScope.of(context).requestFocus(_focusNode);
|
|
130
|
+ }
|
|
131
|
+
|
|
132
|
+ void hideKeyboard() {
|
|
133
|
+ assert(!_disposed);
|
|
134
|
+ _focusNode.unfocus();
|
|
135
|
+ }
|
|
136
|
+}
|
|
137
|
+
|
|
138
|
+class _ZefyrEditorScope extends InheritedWidget {
|
|
139
|
+ final ZefyrEditorScope scope;
|
|
140
|
+
|
|
141
|
+ _ZefyrEditorScope({Key key, Widget child, @required this.scope})
|
|
142
|
+ : super(key: key, child: child);
|
|
143
|
+
|
|
144
|
+ @override
|
|
145
|
+ bool updateShouldNotify(_ZefyrEditorScope oldWidget) {
|
|
146
|
+ return oldWidget.scope != scope;
|
|
147
|
+ }
|
|
148
|
+}
|
|
149
|
+
|
13
|
150
|
/// Widget for editing Zefyr documents.
|
14
|
151
|
class ZefyrEditor extends StatefulWidget {
|
15
|
152
|
const ZefyrEditor({
|
|
@@ -34,119 +171,46 @@ class ZefyrEditor extends StatefulWidget {
|
34
|
171
|
final EdgeInsets padding;
|
35
|
172
|
|
36
|
173
|
static ZefyrEditorScope of(BuildContext context) {
|
37
|
|
- ZefyrEditorScope scope =
|
38
|
|
- context.inheritFromWidgetOfExactType(ZefyrEditorScope);
|
39
|
|
- return scope;
|
|
174
|
+ _ZefyrEditorScope widget =
|
|
175
|
+ context.inheritFromWidgetOfExactType(_ZefyrEditorScope);
|
|
176
|
+ return widget.scope;
|
40
|
177
|
}
|
41
|
178
|
|
42
|
179
|
@override
|
43
|
180
|
_ZefyrEditorState createState() => new _ZefyrEditorState();
|
44
|
181
|
}
|
45
|
182
|
|
46
|
|
-/// Inherited widget which provides access to shared state of a Zefyr editor.
|
47
|
|
-class ZefyrEditorScope extends InheritedWidget {
|
48
|
|
- /// Current selection style
|
49
|
|
- final NotusStyle selectionStyle;
|
50
|
|
- final TextSelection selection;
|
51
|
|
- final FocusOwner focusOwner;
|
52
|
|
- final FocusNode toolbarFocusNode;
|
53
|
|
- final ZefyrImageDelegate imageDelegate;
|
54
|
|
- final ZefyrController _controller;
|
55
|
|
- final FocusNode _focusNode;
|
56
|
|
-
|
57
|
|
- ZefyrEditorScope({
|
58
|
|
- Key key,
|
59
|
|
- @required Widget child,
|
60
|
|
- @required this.selectionStyle,
|
61
|
|
- @required this.selection,
|
62
|
|
- @required this.focusOwner,
|
63
|
|
- @required this.toolbarFocusNode,
|
64
|
|
- @required this.imageDelegate,
|
65
|
|
- @required ZefyrController controller,
|
66
|
|
- @required FocusNode focusNode,
|
67
|
|
- }) : _controller = controller,
|
68
|
|
- _focusNode = focusNode,
|
69
|
|
- super(key: key, child: child);
|
70
|
|
-
|
71
|
|
- void updateSelection(TextSelection value,
|
72
|
|
- {ChangeSource source: ChangeSource.remote}) {
|
73
|
|
- _controller.updateSelection(value, source: source);
|
74
|
|
- }
|
75
|
|
-
|
76
|
|
- void formatSelection(NotusAttribute value) {
|
77
|
|
- _controller.formatSelection(value);
|
78
|
|
- }
|
79
|
|
-
|
80
|
|
- void focus(BuildContext context) {
|
81
|
|
- FocusScope.of(context).requestFocus(_focusNode);
|
82
|
|
- }
|
83
|
|
-
|
84
|
|
- void hideKeyboard() {
|
85
|
|
- _focusNode.unfocus();
|
86
|
|
- }
|
87
|
|
-
|
88
|
|
- @override
|
89
|
|
- bool updateShouldNotify(ZefyrEditorScope oldWidget) {
|
90
|
|
- return (selectionStyle != oldWidget.selectionStyle ||
|
91
|
|
- selection != oldWidget.selection ||
|
92
|
|
- focusOwner != oldWidget.focusOwner ||
|
93
|
|
- imageDelegate != oldWidget.imageDelegate);
|
94
|
|
- }
|
95
|
|
-}
|
96
|
|
-
|
97
|
183
|
class _ZefyrEditorState extends State<ZefyrEditor> {
|
98
|
184
|
final FocusNode _toolbarFocusNode = new FocusNode();
|
99
|
|
-
|
100
|
|
- NotusStyle _selectionStyle;
|
101
|
|
- TextSelection _selection;
|
102
|
|
- FocusOwner _focusOwner;
|
103
|
185
|
ZefyrImageDelegate _imageDelegate;
|
104
|
|
-
|
105
|
|
- FocusOwner getFocusOwner() {
|
106
|
|
- if (widget.focusNode.hasFocus) {
|
107
|
|
- return FocusOwner.editor;
|
108
|
|
- } else if (_toolbarFocusNode.hasFocus) {
|
109
|
|
- return FocusOwner.toolbar;
|
110
|
|
- } else {
|
111
|
|
- return FocusOwner.none;
|
112
|
|
- }
|
113
|
|
- }
|
|
186
|
+ ZefyrEditorScope _scope;
|
114
|
187
|
|
115
|
188
|
@override
|
116
|
189
|
void initState() {
|
117
|
190
|
super.initState();
|
118
|
|
- _selectionStyle = widget.controller.getSelectionStyle();
|
119
|
|
- _selection = widget.controller.selection;
|
120
|
|
- _focusOwner = getFocusOwner();
|
121
|
191
|
_imageDelegate = widget.imageDelegate ?? new ZefyrDefaultImageDelegate();
|
122
|
|
- widget.controller.addListener(_handleControllerChange);
|
123
|
|
- _toolbarFocusNode.addListener(_handleFocusChange);
|
124
|
|
- widget.focusNode.addListener(_handleFocusChange);
|
|
192
|
+ _scope = ZefyrEditorScope(
|
|
193
|
+ toolbarFocusNode: _toolbarFocusNode,
|
|
194
|
+ imageDelegate: _imageDelegate,
|
|
195
|
+ controller: widget.controller,
|
|
196
|
+ focusNode: widget.focusNode,
|
|
197
|
+ );
|
125
|
198
|
}
|
126
|
199
|
|
127
|
200
|
@override
|
128
|
201
|
void didUpdateWidget(ZefyrEditor oldWidget) {
|
129
|
202
|
super.didUpdateWidget(oldWidget);
|
130
|
|
- if (widget.focusNode != oldWidget.focusNode) {
|
131
|
|
- oldWidget.focusNode.removeListener(_handleFocusChange);
|
132
|
|
- widget.focusNode.addListener(_handleFocusChange);
|
133
|
|
- }
|
134
|
|
- if (widget.controller != oldWidget.controller) {
|
135
|
|
- oldWidget.controller.removeListener(_handleControllerChange);
|
136
|
|
- widget.controller.addListener(_handleControllerChange);
|
137
|
|
- _selectionStyle = widget.controller.getSelectionStyle();
|
138
|
|
- _selection = widget.controller.selection;
|
139
|
|
- }
|
|
203
|
+ _scope._updateControllerIfNeeded(widget.controller);
|
|
204
|
+ _scope._updateFocusNodeIfNeeded(widget.focusNode);
|
140
|
205
|
if (widget.imageDelegate != oldWidget.imageDelegate) {
|
141
|
206
|
_imageDelegate = widget.imageDelegate ?? new ZefyrDefaultImageDelegate();
|
|
207
|
+ _scope._updateImageDelegateIfNeeded(_imageDelegate);
|
142
|
208
|
}
|
143
|
209
|
}
|
144
|
210
|
|
145
|
211
|
@override
|
146
|
212
|
void dispose() {
|
147
|
|
- widget.controller.removeListener(_handleControllerChange);
|
148
|
|
- widget.focusNode.removeListener(_handleFocusChange);
|
149
|
|
- _toolbarFocusNode.removeListener(_handleFocusChange);
|
|
213
|
+ _scope.dispose();
|
150
|
214
|
_toolbarFocusNode.dispose();
|
151
|
215
|
super.dispose();
|
152
|
216
|
}
|
|
@@ -165,8 +229,8 @@ class _ZefyrEditorState extends State<ZefyrEditor> {
|
165
|
229
|
final children = <Widget>[];
|
166
|
230
|
children.add(Expanded(child: editable));
|
167
|
231
|
final toolbar = ZefyrToolbar(
|
|
232
|
+ editor: _scope,
|
168
|
233
|
focusNode: _toolbarFocusNode,
|
169
|
|
- controller: widget.controller,
|
170
|
234
|
delegate: widget.toolbarDelegate,
|
171
|
235
|
);
|
172
|
236
|
children.add(toolbar);
|
|
@@ -179,40 +243,10 @@ class _ZefyrEditorState extends State<ZefyrEditor> {
|
179
|
243
|
|
180
|
244
|
return ZefyrTheme(
|
181
|
245
|
data: actualTheme,
|
182
|
|
- child: ZefyrEditorScope(
|
183
|
|
- selection: _selection,
|
184
|
|
- selectionStyle: _selectionStyle,
|
185
|
|
- focusOwner: _focusOwner,
|
186
|
|
- toolbarFocusNode: _toolbarFocusNode,
|
187
|
|
- imageDelegate: _imageDelegate,
|
188
|
|
- controller: widget.controller,
|
189
|
|
- focusNode: widget.focusNode,
|
|
246
|
+ child: _ZefyrEditorScope(
|
|
247
|
+ scope: _scope,
|
190
|
248
|
child: Column(children: children),
|
191
|
249
|
),
|
192
|
250
|
);
|
193
|
251
|
}
|
194
|
|
-
|
195
|
|
- void _handleControllerChange() {
|
196
|
|
- final attrs = widget.controller.getSelectionStyle();
|
197
|
|
- final selection = widget.controller.selection;
|
198
|
|
- if (_selectionStyle != attrs || _selection != selection) {
|
199
|
|
- setState(() {
|
200
|
|
- _selectionStyle = attrs;
|
201
|
|
- _selection = widget.controller.selection;
|
202
|
|
- });
|
203
|
|
- }
|
204
|
|
- }
|
205
|
|
-
|
206
|
|
- void _handleFocusChange() {
|
207
|
|
- setState(() {
|
208
|
|
- _focusOwner = getFocusOwner();
|
209
|
|
- if (_focusOwner == FocusOwner.none && !_selection.isCollapsed) {
|
210
|
|
- // Collapse selection if there is nothing focused.
|
211
|
|
- widget.controller.updateSelection(_selection.copyWith(
|
212
|
|
- baseOffset: _selection.extentOffset,
|
213
|
|
- extentOffset: _selection.extentOffset,
|
214
|
|
- ));
|
215
|
|
- }
|
216
|
|
- });
|
217
|
|
- }
|
218
|
252
|
}
|