@@ -22,6 +22,9 @@ static constexpr char kHideMethod[] = "TextInput.hide";
22
22
static constexpr char kUpdateEditingStateMethod [] =
23
23
" TextInputClient.updateEditingState" ;
24
24
static constexpr char kPerformActionMethod [] = " TextInputClient.performAction" ;
25
+ static constexpr char kSetEditableSizeAndTransform [] =
26
+ " TextInput.setEditableSizeAndTransform" ;
27
+ static constexpr char kSetMarkedTextRect [] = " TextInput.setMarkedTextRect" ;
25
28
26
29
static constexpr char kInputActionKey [] = " inputAction" ;
27
30
static constexpr char kTextInputTypeKey [] = " inputType" ;
@@ -34,6 +37,8 @@ static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional";
34
37
static constexpr char kComposingBaseKey [] = " composingBase" ;
35
38
static constexpr char kComposingExtentKey [] = " composingExtent" ;
36
39
40
+ static constexpr char kTransform [] = " transform" ;
41
+
37
42
static constexpr char kTextAffinityDownstream [] = " TextAffinity.downstream" ;
38
43
static constexpr char kMultilineInputType [] = " TextInputType.multiline" ;
39
44
@@ -57,6 +62,23 @@ struct _FlTextInputPlugin {
57
62
GtkIMContext* im_context;
58
63
59
64
flutter::TextInputModel* text_model;
65
+
66
+ // The owning Flutter view.
67
+ FlView* view;
68
+
69
+ // A 4x4 matrix that maps from `EditableText` local coordinates to the
70
+ // coordinate system of `PipelineOwner.rootNode`.
71
+ double editabletext_transform[4 ][4 ];
72
+
73
+ // The smallest rect, in local coordinates, of the text in the composing
74
+ // range, or of the caret in the case where there is no current composing
75
+ // range. This value is updated via `TextInput.setMarkedTextRect` messages
76
+ // over the text input channel.
77
+ GdkRectangle composing_rect;
78
+
79
+ // A cached copy of the composing (pre-edit in Gtk terminology) rect in
80
+ // window coordinates.
81
+ GdkRectangle preedit_rect;
60
82
};
61
83
62
84
G_DEFINE_TYPE (FlTextInputPlugin, fl_text_input_plugin, G_TYPE_OBJECT)
@@ -100,13 +122,20 @@ static void update_editing_state(FlTextInputPlugin* self) {
100
122
value, kSelectionExtentKey ,
101
123
fl_value_new_int (self->text_model ->selection_extent ()));
102
124
125
+ int composing_base =
126
+ self->text_model ->composing () ? self->text_model ->composing_base () : -1 ;
127
+ int composing_extent =
128
+ self->text_model ->composing () ? self->text_model ->composing_extent () : -1 ;
129
+ fl_value_set_string_take (value, kComposingBaseKey ,
130
+ fl_value_new_int (composing_base));
131
+ fl_value_set_string_take (value, kComposingExtentKey ,
132
+ fl_value_new_int (composing_extent));
133
+
103
134
// The following keys are not implemented and set to default values.
104
135
fl_value_set_string_take (value, kSelectionAffinityKey ,
105
136
fl_value_new_string (kTextAffinityDownstream ));
106
137
fl_value_set_string_take (value, kSelectionIsDirectionalKey ,
107
138
fl_value_new_bool (FALSE ));
108
- fl_value_set_string_take (value, kComposingBaseKey , fl_value_new_int (-1 ));
109
- fl_value_set_string_take (value, kComposingExtentKey , fl_value_new_int (-1 ));
110
139
111
140
fl_value_append (args, value);
112
141
@@ -139,9 +168,50 @@ static void perform_action(FlTextInputPlugin* self) {
139
168
nullptr , perform_action_response_cb, self);
140
169
}
141
170
171
+ // Signal handler for GtkIMContext::preedit-start
172
+ static void im_preedit_start_cb (FlTextInputPlugin* self) {
173
+ self->text_model ->BeginComposing ();
174
+
175
+ // Set the top-level window used for system input method windows.
176
+ GdkWindow* window =
177
+ gtk_widget_get_window (gtk_widget_get_toplevel (GTK_WIDGET (self->view )));
178
+ gtk_im_context_set_client_window (self->im_context , window);
179
+
180
+ // Set the cursor location in window co-ordinates so that Gtk can position
181
+ // any system input method windows.
182
+ gtk_im_context_set_cursor_location (self->im_context , &self->preedit_rect );
183
+ }
184
+
185
+ // Signal handler for GtkIMContext::preedit-changed
186
+ static void im_preedit_changed_cb (FlTextInputPlugin* self) {
187
+ // Set the cursor location in window co-ordinates so that Gtk can position
188
+ // any system input method windows.
189
+ gtk_im_context_set_cursor_location (self->im_context , &self->preedit_rect );
190
+
191
+ gchar* buf = nullptr ;
192
+ gint cursor_offset = 0 ;
193
+ gtk_im_context_get_preedit_string (self->im_context , &buf, NULL ,
194
+ &cursor_offset);
195
+ cursor_offset += self->text_model ->composing_base ();
196
+ self->text_model ->UpdateComposingText (buf);
197
+ self->text_model ->SetSelection (cursor_offset, cursor_offset);
198
+
199
+ update_editing_state (self);
200
+ }
201
+
142
202
// Signal handler for GtkIMContext::commit
143
203
static void im_commit_cb (FlTextInputPlugin* self, const gchar* text) {
144
- self->text_model ->AddText (text);
204
+ if (self->text_model ->composing ()) {
205
+ self->text_model ->CommitComposing ();
206
+ } else {
207
+ self->text_model ->AddText (text);
208
+ }
209
+ update_editing_state (self);
210
+ }
211
+
212
+ // Signal handler for GtkIMContext::preedit-end
213
+ static void im_preedit_end_cb (FlTextInputPlugin* self) {
214
+ self->text_model ->EndComposing ();
145
215
update_editing_state (self);
146
216
}
147
217
@@ -209,6 +279,8 @@ static FlMethodResponse* set_editing_state(FlTextInputPlugin* self,
209
279
FlValue* args) {
210
280
const gchar* text =
211
281
fl_value_get_string (fl_value_lookup_string (args, kTextKey ));
282
+ self->text_model ->SetText (text);
283
+
212
284
int64_t selection_base =
213
285
fl_value_get_int (fl_value_lookup_string (args, kSelectionBaseKey ));
214
286
int64_t selection_extent =
@@ -217,10 +289,21 @@ static FlMethodResponse* set_editing_state(FlTextInputPlugin* self,
217
289
if (selection_base == -1 && selection_extent == -1 ) {
218
290
selection_base = selection_extent = 0 ;
219
291
}
220
-
221
- self->text_model ->SetText (text);
222
292
self->text_model ->SetSelection (selection_base, selection_extent);
223
293
294
+ int64_t composing_base =
295
+ fl_value_get_int (fl_value_lookup_string (args, kComposingBaseKey ));
296
+ int64_t composing_extent =
297
+ fl_value_get_int (fl_value_lookup_string (args, kComposingExtentKey ));
298
+ if (composing_base == -1 && composing_extent == -1 ) {
299
+ self->text_model ->EndComposing ();
300
+ } else {
301
+ size_t composing_start = std::min (composing_base, composing_extent);
302
+ size_t cursor_offset = selection_base - composing_start;
303
+ self->text_model ->SetComposingRange (composing_base, composing_extent,
304
+ cursor_offset);
305
+ }
306
+
224
307
return FL_METHOD_RESPONSE (fl_method_success_response_new (nullptr ));
225
308
}
226
309
@@ -238,6 +321,70 @@ static FlMethodResponse* hide(FlTextInputPlugin* self) {
238
321
return FL_METHOD_RESPONSE (fl_method_success_response_new (nullptr ));
239
322
}
240
323
324
+ // Update the local copy of the preedit (composing) rect.
325
+ //
326
+ // As text is input by the user, the framework sends two streams of updates
327
+ // over the text input channel: updates to the composing rect (cursor rect when
328
+ // not in IME composing mode) and updates to the matrix transform from local
329
+ // coordinates to Flutter root coordinates. This function is called after each
330
+ // of these updates.
331
+ static void update_preedit_rect (FlTextInputPlugin* self) {
332
+ // Skip update if not composing to avoid setting to position 0.
333
+ if (!self->text_model ->composing ()) {
334
+ return ;
335
+ }
336
+
337
+ // Transform the x, y positions of the cursor from local coordinates to
338
+ // Flutter view coordinates.
339
+ gint x = self->composing_rect .x * self->editabletext_transform [0 ][0 ] +
340
+ self->composing_rect .y * self->editabletext_transform [1 ][0 ] +
341
+ self->editabletext_transform [3 ][0 ] + self->composing_rect .width ;
342
+ gint y = self->composing_rect .x * self->editabletext_transform [0 ][1 ] +
343
+ self->composing_rect .y * self->editabletext_transform [1 ][1 ] +
344
+ self->editabletext_transform [3 ][1 ] + self->composing_rect .height ;
345
+
346
+ // Transform from Flutter view coordinates to Gtk window coordinates and
347
+ // write them to preedit_rect.
348
+ gtk_widget_translate_coordinates (
349
+ GTK_WIDGET (self->view ), gtk_widget_get_toplevel (GTK_WIDGET (self->view )),
350
+ x, y, &self->preedit_rect .x , &self->preedit_rect .y );
351
+ }
352
+
353
+ // Handles updates from the framework to the cached coordinate transform of the
354
+ // `EditableText` relative to the root coordinate system.
355
+ static FlMethodResponse* set_editable_size_and_transform (
356
+ FlTextInputPlugin* self,
357
+ FlValue* args) {
358
+ FlValue* transform = fl_value_lookup_string (args, kTransform );
359
+ size_t transform_len = fl_value_get_length (transform);
360
+ g_warn_if_fail (transform_len == 16 );
361
+
362
+ for (size_t i = 0 ; i < transform_len; ++i) {
363
+ double val = fl_value_get_float (fl_value_get_list_value (transform, i));
364
+ self->editabletext_transform [i / 4 ][i % 4 ] = val;
365
+ }
366
+ update_preedit_rect (self);
367
+
368
+ return FL_METHOD_RESPONSE (fl_method_success_response_new (nullptr ));
369
+ }
370
+
371
+ // Handles updates from the framework to the cached composing rect in local
372
+ // coordinates.
373
+ static FlMethodResponse* set_marked_text_rect (FlTextInputPlugin* self,
374
+ FlValue* args) {
375
+ self->composing_rect .x =
376
+ fl_value_get_float (fl_value_lookup_string (args, " x" ));
377
+ self->composing_rect .y =
378
+ fl_value_get_float (fl_value_lookup_string (args, " y" ));
379
+ self->composing_rect .width =
380
+ fl_value_get_float (fl_value_lookup_string (args, " width" ));
381
+ self->composing_rect .height =
382
+ fl_value_get_float (fl_value_lookup_string (args, " height" ));
383
+ update_preedit_rect (self);
384
+
385
+ return FL_METHOD_RESPONSE (fl_method_success_response_new (nullptr ));
386
+ }
387
+
241
388
// Called when a method call is received from Flutter.
242
389
static void method_call_cb (FlMethodChannel* channel,
243
390
FlMethodCall* method_call,
@@ -258,6 +405,10 @@ static void method_call_cb(FlMethodChannel* channel,
258
405
response = clear_client (self);
259
406
} else if (strcmp (method, kHideMethod ) == 0 ) {
260
407
response = hide (self);
408
+ } else if (strcmp (method, kSetEditableSizeAndTransform ) == 0 ) {
409
+ response = set_editable_size_and_transform (self, args);
410
+ } else if (strcmp (method, kSetMarkedTextRect ) == 0 ) {
411
+ response = set_marked_text_rect (self, args);
261
412
} else {
262
413
response = FL_METHOD_RESPONSE (fl_method_not_implemented_response_new ());
263
414
}
@@ -268,6 +419,11 @@ static void method_call_cb(FlMethodChannel* channel,
268
419
}
269
420
}
270
421
422
+ static void view_weak_notify_cb (gpointer user_data, GObject* object) {
423
+ FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN (object);
424
+ self->view = nullptr ;
425
+ }
426
+
271
427
static void fl_text_input_plugin_dispose (GObject* object) {
272
428
FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN (object);
273
429
@@ -290,6 +446,15 @@ static void fl_text_input_plugin_init(FlTextInputPlugin* self) {
290
446
self->client_id = kClientIdUnset ;
291
447
self->im_context = gtk_im_multicontext_new ();
292
448
self->input_multiline = FALSE ;
449
+ g_signal_connect_object (self->im_context , " preedit-start" ,
450
+ G_CALLBACK (im_preedit_start_cb), self,
451
+ G_CONNECT_SWAPPED);
452
+ g_signal_connect_object (self->im_context , " preedit-end" ,
453
+ G_CALLBACK (im_preedit_end_cb), self,
454
+ G_CONNECT_SWAPPED);
455
+ g_signal_connect_object (self->im_context , " preedit-changed" ,
456
+ G_CALLBACK (im_preedit_changed_cb), self,
457
+ G_CONNECT_SWAPPED);
293
458
g_signal_connect_object (self->im_context , " commit" , G_CALLBACK (im_commit_cb),
294
459
self, G_CONNECT_SWAPPED);
295
460
g_signal_connect_object (self->im_context , " retrieve-surrounding" ,
@@ -301,7 +466,8 @@ static void fl_text_input_plugin_init(FlTextInputPlugin* self) {
301
466
self->text_model = new flutter::TextInputModel ();
302
467
}
303
468
304
- FlTextInputPlugin* fl_text_input_plugin_new (FlBinaryMessenger* messenger) {
469
+ FlTextInputPlugin* fl_text_input_plugin_new (FlBinaryMessenger* messenger,
470
+ FlView* view) {
305
471
g_return_val_if_fail (FL_IS_BINARY_MESSENGER (messenger), nullptr );
306
472
307
473
FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN (
@@ -312,7 +478,8 @@ FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger) {
312
478
fl_method_channel_new (messenger, kChannelName , FL_METHOD_CODEC (codec));
313
479
fl_method_channel_set_method_call_handler (self->channel , method_call_cb, self,
314
480
nullptr );
315
-
481
+ self->view = view;
482
+ g_object_weak_ref (G_OBJECT (view), view_weak_notify_cb, self);
316
483
return self;
317
484
}
318
485
0 commit comments