@@ -22,6 +22,9 @@ static constexpr char kHideMethod[] = "TextInput.hide";
2222static constexpr char kUpdateEditingStateMethod [] =
2323 " TextInputClient.updateEditingState" ;
2424static constexpr char kPerformActionMethod [] = " TextInputClient.performAction" ;
25+ static constexpr char kSetEditableSizeAndTransform [] =
26+ " TextInput.setEditableSizeAndTransform" ;
27+ static constexpr char kSetMarkedTextRect [] = " TextInput.setMarkedTextRect" ;
2528
2629static constexpr char kInputActionKey [] = " inputAction" ;
2730static constexpr char kTextInputTypeKey [] = " inputType" ;
@@ -34,6 +37,8 @@ static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional";
3437static constexpr char kComposingBaseKey [] = " composingBase" ;
3538static constexpr char kComposingExtentKey [] = " composingExtent" ;
3639
40+ static constexpr char kTransform [] = " transform" ;
41+
3742static constexpr char kTextAffinityDownstream [] = " TextAffinity.downstream" ;
3843static constexpr char kMultilineInputType [] = " TextInputType.multiline" ;
3944
@@ -57,6 +62,19 @@ struct _FlTextInputPlugin {
5762 GtkIMContext* im_context;
5863
5964 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;
6078};
6179
6280G_DEFINE_TYPE (FlTextInputPlugin, fl_text_input_plugin, G_TYPE_OBJECT)
@@ -99,13 +117,22 @@ static void update_editing_state(FlTextInputPlugin* self) {
99117 fl_value_set_string_take (value, kSelectionExtentKey ,
100118 fl_value_new_int (selection.extent ()));
101119
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+
102131 // The following keys are not implemented and set to default values.
103132 fl_value_set_string_take (value, kSelectionAffinityKey ,
104133 fl_value_new_string (kTextAffinityDownstream ));
105134 fl_value_set_string_take (value, kSelectionIsDirectionalKey ,
106135 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 ));
109136
110137 fl_value_append (args, value);
111138
@@ -138,9 +165,41 @@ static void perform_action(FlTextInputPlugin* self) {
138165 nullptr , perform_action_response_cb, self);
139166}
140167
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+ g_autofree gchar* buf = nullptr ;
181+ gint cursor_offset = 0 ;
182+ gtk_im_context_get_preedit_string (self->im_context , &buf, nullptr ,
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+
141191// Signal handler for GtkIMContext::commit
142192static void im_commit_cb (FlTextInputPlugin* self, const gchar* text) {
143193 self->text_model ->AddText (text);
194+ if (self->text_model ->composing ()) {
195+ self->text_model ->CommitComposing ();
196+ }
197+ update_editing_state (self);
198+ }
199+
200+ // Signal handler for GtkIMContext::preedit-end
201+ static void im_preedit_end_cb (FlTextInputPlugin* self) {
202+ self->text_model ->EndComposing ();
144203 update_editing_state (self);
145204}
146205
@@ -209,6 +268,8 @@ static FlMethodResponse* set_editing_state(FlTextInputPlugin* self,
209268 FlValue* args) {
210269 const gchar* text =
211270 fl_value_get_string (fl_value_lookup_string (args, kTextKey ));
271+ self->text_model ->SetText (text);
272+
212273 int64_t selection_base =
213274 fl_value_get_int (fl_value_lookup_string (args, kSelectionBaseKey ));
214275 int64_t selection_extent =
@@ -221,6 +282,19 @@ static FlMethodResponse* set_editing_state(FlTextInputPlugin* self,
221282 self->text_model ->SetText (text);
222283 self->text_model ->SetSelection (TextRange (selection_base, selection_extent));
223284
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+
224298 return FL_METHOD_RESPONSE (fl_method_success_response_new (nullptr ));
225299}
226300
@@ -238,6 +312,83 @@ static FlMethodResponse* hide(FlTextInputPlugin* self) {
238312 return FL_METHOD_RESPONSE (fl_method_success_response_new (nullptr ));
239313}
240314
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+ // Transform from Flutter view coordinates to GTK window coordinates.
339+ GdkRectangle preedit_rect;
340+ gtk_widget_translate_coordinates (
341+ GTK_WIDGET (self->view ), gtk_widget_get_toplevel (GTK_WIDGET (self->view )),
342+ x, y, &preedit_rect.x , &preedit_rect.y );
343+
344+ // Set the cursor location in window coordinates so that GTK can position any
345+ // system input method windows.
346+ gtk_im_context_set_cursor_location (self->im_context , &preedit_rect);
347+ }
348+
349+ // Handles updates to the EditableText size and position from the framework.
350+ //
351+ // On changes to the size or position of the RenderObject underlying the
352+ // EditableText, this update may be triggered. It provides an updated size and
353+ // transform from the local coordinate system of the EditableText to root
354+ // Flutter 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_im_cursor_position (self);
367+
368+ return FL_METHOD_RESPONSE (fl_method_success_response_new (nullptr ));
369+ }
370+
371+ // Handles updates to the composing rect from the framework.
372+ //
373+ // On changes to the state of the EditableText in the framework, this update
374+ // may be triggered. It provides an updated rect for the composing region in
375+ // local coordinates of the EditableText. In the case where there is no
376+ // composing region, the cursor rect is sent.
377+ static FlMethodResponse* set_marked_text_rect (FlTextInputPlugin* self,
378+ FlValue* args) {
379+ self->composing_rect .x =
380+ fl_value_get_float (fl_value_lookup_string (args, " x" ));
381+ self->composing_rect .y =
382+ fl_value_get_float (fl_value_lookup_string (args, " y" ));
383+ self->composing_rect .width =
384+ fl_value_get_float (fl_value_lookup_string (args, " width" ));
385+ self->composing_rect .height =
386+ fl_value_get_float (fl_value_lookup_string (args, " height" ));
387+ update_im_cursor_position (self);
388+
389+ return FL_METHOD_RESPONSE (fl_method_success_response_new (nullptr ));
390+ }
391+
241392// Called when a method call is received from Flutter.
242393static void method_call_cb (FlMethodChannel* channel,
243394 FlMethodCall* method_call,
@@ -258,6 +409,10 @@ static void method_call_cb(FlMethodChannel* channel,
258409 response = clear_client (self);
259410 } else if (strcmp (method, kHideMethod ) == 0 ) {
260411 response = hide (self);
412+ } else if (strcmp (method, kSetEditableSizeAndTransform ) == 0 ) {
413+ response = set_editable_size_and_transform (self, args);
414+ } else if (strcmp (method, kSetMarkedTextRect ) == 0 ) {
415+ response = set_marked_text_rect (self, args);
261416 } else {
262417 response = FL_METHOD_RESPONSE (fl_method_not_implemented_response_new ());
263418 }
@@ -268,6 +423,11 @@ static void method_call_cb(FlMethodChannel* channel,
268423 }
269424}
270425
426+ static void view_weak_notify_cb (gpointer user_data, GObject* object) {
427+ FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN (object);
428+ self->view = nullptr ;
429+ }
430+
271431static void fl_text_input_plugin_dispose (GObject* object) {
272432 FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN (object);
273433
@@ -290,6 +450,15 @@ static void fl_text_input_plugin_init(FlTextInputPlugin* self) {
290450 self->client_id = kClientIdUnset ;
291451 self->im_context = gtk_im_multicontext_new ();
292452 self->input_multiline = FALSE ;
453+ g_signal_connect_object (self->im_context , " preedit-start" ,
454+ G_CALLBACK (im_preedit_start_cb), self,
455+ G_CONNECT_SWAPPED);
456+ g_signal_connect_object (self->im_context , " preedit-end" ,
457+ G_CALLBACK (im_preedit_end_cb), self,
458+ G_CONNECT_SWAPPED);
459+ g_signal_connect_object (self->im_context , " preedit-changed" ,
460+ G_CALLBACK (im_preedit_changed_cb), self,
461+ G_CONNECT_SWAPPED);
293462 g_signal_connect_object (self->im_context , " commit" , G_CALLBACK (im_commit_cb),
294463 self, G_CONNECT_SWAPPED);
295464 g_signal_connect_object (self->im_context , " retrieve-surrounding" ,
@@ -301,7 +470,8 @@ static void fl_text_input_plugin_init(FlTextInputPlugin* self) {
301470 self->text_model = new flutter::TextInputModel ();
302471}
303472
304- FlTextInputPlugin* fl_text_input_plugin_new (FlBinaryMessenger* messenger) {
473+ FlTextInputPlugin* fl_text_input_plugin_new (FlBinaryMessenger* messenger,
474+ FlView* view) {
305475 g_return_val_if_fail (FL_IS_BINARY_MESSENGER (messenger), nullptr );
306476
307477 FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN (
@@ -312,6 +482,8 @@ FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger) {
312482 fl_method_channel_new (messenger, kChannelName , FL_METHOD_CODEC (codec));
313483 fl_method_channel_set_method_call_handler (self->channel , method_call_cb, self,
314484 nullptr );
485+ self->view = view;
486+ g_object_weak_ref (G_OBJECT (view), view_weak_notify_cb, self);
315487
316488 return self;
317489}
0 commit comments