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