@@ -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,19 @@ 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;
60
78
};
61
79
62
80
G_DEFINE_TYPE (FlTextInputPlugin, fl_text_input_plugin, G_TYPE_OBJECT)
@@ -99,13 +117,22 @@ static void update_editing_state(FlTextInputPlugin* self) {
99
117
fl_value_set_string_take (value, kSelectionExtentKey ,
100
118
fl_value_new_int (selection.extent ()));
101
119
120
+ int composing_base = self->text_model ->composing ()
121
+ ? self->text_model ->composing_range ().base ()
122
+ : -1 ;
123
+ int composing_extent = self->text_model ->composing ()
124
+ ? self->text_model ->composing_range ().extent ()
125
+ : -1 ;
126
+ fl_value_set_string_take (value, kComposingBaseKey ,
127
+ fl_value_new_int (composing_base));
128
+ fl_value_set_string_take (value, kComposingExtentKey ,
129
+ fl_value_new_int (composing_extent));
130
+
102
131
// The following keys are not implemented and set to default values.
103
132
fl_value_set_string_take (value, kSelectionAffinityKey ,
104
133
fl_value_new_string (kTextAffinityDownstream ));
105
134
fl_value_set_string_take (value, kSelectionIsDirectionalKey ,
106
135
fl_value_new_bool (FALSE ));
107
- fl_value_set_string_take (value, kComposingBaseKey , fl_value_new_int (-1 ));
108
- fl_value_set_string_take (value, kComposingExtentKey , fl_value_new_int (-1 ));
109
136
110
137
fl_value_append (args, value);
111
138
@@ -138,9 +165,42 @@ static void perform_action(FlTextInputPlugin* self) {
138
165
nullptr , perform_action_response_cb, self);
139
166
}
140
167
168
+ // Signal handler for GtkIMContext::preedit-start
169
+ static void im_preedit_start_cb (FlTextInputPlugin* self) {
170
+ self->text_model ->BeginComposing ();
171
+
172
+ // Set the top-level window used for system input method windows.
173
+ GdkWindow* window =
174
+ gtk_widget_get_window (gtk_widget_get_toplevel (GTK_WIDGET (self->view )));
175
+ gtk_im_context_set_client_window (self->im_context , window);
176
+ }
177
+
178
+ // Signal handler for GtkIMContext::preedit-changed
179
+ static void im_preedit_changed_cb (FlTextInputPlugin* self) {
180
+ gchar* buf = nullptr ;
181
+ gint cursor_offset = 0 ;
182
+ gtk_im_context_get_preedit_string (self->im_context , &buf, NULL ,
183
+ &cursor_offset);
184
+ cursor_offset += self->text_model ->composing_range ().base ();
185
+ self->text_model ->UpdateComposingText (buf);
186
+ self->text_model ->SetSelection (TextRange (cursor_offset, cursor_offset));
187
+
188
+ update_editing_state (self);
189
+ }
190
+
141
191
// Signal handler for GtkIMContext::commit
142
192
static void im_commit_cb (FlTextInputPlugin* self, const gchar* text) {
143
- self->text_model ->AddText (text);
193
+ if (self->text_model ->composing ()) {
194
+ self->text_model ->CommitComposing ();
195
+ } else {
196
+ self->text_model ->AddText (text);
197
+ }
198
+ update_editing_state (self);
199
+ }
200
+
201
+ // Signal handler for GtkIMContext::preedit-end
202
+ static void im_preedit_end_cb (FlTextInputPlugin* self) {
203
+ self->text_model ->EndComposing ();
144
204
update_editing_state (self);
145
205
}
146
206
@@ -208,6 +268,8 @@ static FlMethodResponse* set_editing_state(FlTextInputPlugin* self,
208
268
FlValue* args) {
209
269
const gchar* text =
210
270
fl_value_get_string (fl_value_lookup_string (args, kTextKey ));
271
+ self->text_model ->SetText (text);
272
+
211
273
int64_t selection_base =
212
274
fl_value_get_int (fl_value_lookup_string (args, kSelectionBaseKey ));
213
275
int64_t selection_extent =
@@ -220,6 +282,19 @@ static FlMethodResponse* set_editing_state(FlTextInputPlugin* self,
220
282
self->text_model ->SetText (text);
221
283
self->text_model ->SetSelection (TextRange (selection_base, selection_extent));
222
284
285
+ int64_t composing_base =
286
+ fl_value_get_int (fl_value_lookup_string (args, kComposingBaseKey ));
287
+ int64_t composing_extent =
288
+ fl_value_get_int (fl_value_lookup_string (args, kComposingExtentKey ));
289
+ if (composing_base == -1 && composing_extent == -1 ) {
290
+ self->text_model ->EndComposing ();
291
+ } else {
292
+ size_t composing_start = std::min (composing_base, composing_extent);
293
+ size_t cursor_offset = selection_base - composing_start;
294
+ self->text_model ->SetComposingRange (
295
+ TextRange (composing_base, composing_extent), cursor_offset);
296
+ }
297
+
223
298
return FL_METHOD_RESPONSE (fl_method_success_response_new (nullptr ));
224
299
}
225
300
@@ -237,6 +312,84 @@ static FlMethodResponse* hide(FlTextInputPlugin* self) {
237
312
return FL_METHOD_RESPONSE (fl_method_success_response_new (nullptr ));
238
313
}
239
314
315
+ // Update the IM cursor position.
316
+ //
317
+ // As text is input by the user, the framework sends two streams of updates
318
+ // over the text input channel: updates to the composing rect (cursor rect when
319
+ // not in IME composing mode) and updates to the matrix transform from local
320
+ // coordinates to Flutter root coordinates. This function is called after each
321
+ // of these updates. It transforms the composing rect to Gtk window coordinates
322
+ // and notifies Gtk of the updated cursor position.
323
+ static void update_im_cursor_position (FlTextInputPlugin* self) {
324
+ // Skip update if not composing to avoid setting to position 0.
325
+ if (!self->text_model ->composing ()) {
326
+ return ;
327
+ }
328
+
329
+ // Transform the x, y positions of the cursor from local coordinates to
330
+ // Flutter view coordinates.
331
+ gint x = self->composing_rect .x * self->editabletext_transform [0 ][0 ] +
332
+ self->composing_rect .y * self->editabletext_transform [1 ][0 ] +
333
+ self->editabletext_transform [3 ][0 ] + self->composing_rect .width ;
334
+ gint y = self->composing_rect .x * self->editabletext_transform [0 ][1 ] +
335
+ self->composing_rect .y * self->editabletext_transform [1 ][1 ] +
336
+ self->editabletext_transform [3 ][1 ] + self->composing_rect .height ;
337
+
338
+
339
+ // Transform from Flutter view coordinates to Gtk window coordinates.
340
+ GdkRectangle preedit_rect;
341
+ gtk_widget_translate_coordinates (
342
+ GTK_WIDGET (self->view ), gtk_widget_get_toplevel (GTK_WIDGET (self->view )),
343
+ x, y, &preedit_rect.x , &preedit_rect.y );
344
+
345
+ // Set the cursor location in window coordinates so that Gtk can position any
346
+ // system input method windows.
347
+ gtk_im_context_set_cursor_location (self->im_context , &preedit_rect);
348
+ }
349
+
350
+ // Handles updates to the EditableText size and position from the framework.
351
+ //
352
+ // On changes to the size or position of the RenderObject underlying the
353
+ // EditableText, this update may be triggered. It provides an updated size and
354
+ // transform from the local coordinate system of the EditableText to root
355
+ // Flutter coordinate system.
356
+ static FlMethodResponse* set_editable_size_and_transform (
357
+ FlTextInputPlugin* self,
358
+ FlValue* args) {
359
+ FlValue* transform = fl_value_lookup_string (args, kTransform );
360
+ size_t transform_len = fl_value_get_length (transform);
361
+ g_warn_if_fail (transform_len == 16 );
362
+
363
+ for (size_t i = 0 ; i < transform_len; ++i) {
364
+ double val = fl_value_get_float (fl_value_get_list_value (transform, i));
365
+ self->editabletext_transform [i / 4 ][i % 4 ] = val;
366
+ }
367
+ update_im_cursor_position (self);
368
+
369
+ return FL_METHOD_RESPONSE (fl_method_success_response_new (nullptr ));
370
+ }
371
+
372
+ // Handles updates to the composing rect from the framework.
373
+ //
374
+ // On changes to the state of the EditableText in the framework, this update
375
+ // may be triggered. It provides an updated rect for the composing region in
376
+ // local coordinates of the EditableText. In the case where there is no
377
+ // composing region, the cursor rect is sent.
378
+ static FlMethodResponse* set_marked_text_rect (FlTextInputPlugin* self,
379
+ FlValue* args) {
380
+ self->composing_rect .x =
381
+ fl_value_get_float (fl_value_lookup_string (args, " x" ));
382
+ self->composing_rect .y =
383
+ fl_value_get_float (fl_value_lookup_string (args, " y" ));
384
+ self->composing_rect .width =
385
+ fl_value_get_float (fl_value_lookup_string (args, " width" ));
386
+ self->composing_rect .height =
387
+ fl_value_get_float (fl_value_lookup_string (args, " height" ));
388
+ update_im_cursor_position (self);
389
+
390
+ return FL_METHOD_RESPONSE (fl_method_success_response_new (nullptr ));
391
+ }
392
+
240
393
// Called when a method call is received from Flutter.
241
394
static void method_call_cb (FlMethodChannel* channel,
242
395
FlMethodCall* method_call,
@@ -257,6 +410,10 @@ static void method_call_cb(FlMethodChannel* channel,
257
410
response = clear_client (self);
258
411
} else if (strcmp (method, kHideMethod ) == 0 ) {
259
412
response = hide (self);
413
+ } else if (strcmp (method, kSetEditableSizeAndTransform ) == 0 ) {
414
+ response = set_editable_size_and_transform (self, args);
415
+ } else if (strcmp (method, kSetMarkedTextRect ) == 0 ) {
416
+ response = set_marked_text_rect (self, args);
260
417
} else {
261
418
response = FL_METHOD_RESPONSE (fl_method_not_implemented_response_new ());
262
419
}
@@ -267,6 +424,11 @@ static void method_call_cb(FlMethodChannel* channel,
267
424
}
268
425
}
269
426
427
+ static void view_weak_notify_cb (gpointer user_data, GObject* object) {
428
+ FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN (object);
429
+ self->view = nullptr ;
430
+ }
431
+
270
432
static void fl_text_input_plugin_dispose (GObject* object) {
271
433
FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN (object);
272
434
@@ -289,6 +451,15 @@ static void fl_text_input_plugin_init(FlTextInputPlugin* self) {
289
451
self->client_id = kClientIdUnset ;
290
452
self->im_context = gtk_im_multicontext_new ();
291
453
self->input_multiline = FALSE ;
454
+ g_signal_connect_object (self->im_context , " preedit-start" ,
455
+ G_CALLBACK (im_preedit_start_cb), self,
456
+ G_CONNECT_SWAPPED);
457
+ g_signal_connect_object (self->im_context , " preedit-end" ,
458
+ G_CALLBACK (im_preedit_end_cb), self,
459
+ G_CONNECT_SWAPPED);
460
+ g_signal_connect_object (self->im_context , " preedit-changed" ,
461
+ G_CALLBACK (im_preedit_changed_cb), self,
462
+ G_CONNECT_SWAPPED);
292
463
g_signal_connect_object (self->im_context , " commit" , G_CALLBACK (im_commit_cb),
293
464
self, G_CONNECT_SWAPPED);
294
465
g_signal_connect_object (self->im_context , " retrieve-surrounding" ,
@@ -300,7 +471,8 @@ static void fl_text_input_plugin_init(FlTextInputPlugin* self) {
300
471
self->text_model = new flutter::TextInputModel ();
301
472
}
302
473
303
- FlTextInputPlugin* fl_text_input_plugin_new (FlBinaryMessenger* messenger) {
474
+ FlTextInputPlugin* fl_text_input_plugin_new (FlBinaryMessenger* messenger,
475
+ FlView* view) {
304
476
g_return_val_if_fail (FL_IS_BINARY_MESSENGER (messenger), nullptr );
305
477
306
478
FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN (
@@ -311,7 +483,8 @@ FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger) {
311
483
fl_method_channel_new (messenger, kChannelName , FL_METHOD_CODEC (codec));
312
484
fl_method_channel_set_method_call_handler (self->channel , method_call_cb, self,
313
485
nullptr );
314
-
486
+ self->view = view;
487
+ g_object_weak_ref (G_OBJECT (view), view_weak_notify_cb, self);
315
488
return self;
316
489
}
317
490
0 commit comments