From e4a101abe00881ffd1c0511dbfe3392a3940a1b6 Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Sat, 10 Apr 2021 01:31:42 +0200 Subject: [PATCH 1/6] Fix accent popup position https://github.com/flutter/flutter/issues/80164 --- .../framework/Source/FlutterTextInputPlugin.mm | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm index 0a948d550117c..de7b7e1f7025d 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm @@ -21,6 +21,8 @@ static NSString* const kHideMethod = @"TextInput.hide"; static NSString* const kClearClientMethod = @"TextInput.clearClient"; static NSString* const kSetEditingStateMethod = @"TextInput.setEditingState"; +static NSString* const kSetEditableSizeAndTransform = @"TextInput.setEditableSizeAndTransform"; +static NSString* const kSetCaretRect = @"TextInput.setCaretRect"; static NSString* const kUpdateEditStateResponseMethod = @"TextInputClient.updateEditingState"; static NSString* const kPerformAction = @"TextInputClient.performAction"; static NSString* const kMultilineInputType = @"TextInputType.multiline"; @@ -39,6 +41,7 @@ static NSString* const kComposingBaseKey = @"composingBase"; static NSString* const kComposingExtentKey = @"composingExtent"; static NSString* const kTextKey = @"text"; +static NSString* const kTransformKey = @"transform"; /** * The affinity of the current cursor position. If the cursor is at a position representing @@ -163,6 +166,8 @@ - (instancetype)initWithViewController:(FlutterViewController*)viewController { _textInputContext = [[NSTextInputContext alloc] initWithClient:self]; _previouslyPressedFlags = 0; + self.flutterViewController = viewController; + // Initialize with the zero matrix which is not // an affine transform. _editableTransform = CATransform3D(); @@ -215,10 +220,10 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { // engine since it sent this update, and needs to now be made to match the // engine's version of the state. [self updateEditState]; - } else if ([method isEqualToString:@"TextInput.setEditableSizeAndTransform"]) { + } else if ([method isEqualToString:kSetEditableSizeAndTransform]) { NSDictionary* state = call.arguments; - [self setEditableTransform:state[@"transform"]]; - } else if ([method isEqualToString:@"TextInput.setCaretRect"]) { + [self setEditableTransform:state[kTransformKey]]; + } else if ([method isEqualToString:kSetCaretRect]) { NSDictionary* rect = call.arguments; [self updateCaretRect:rect]; } else { @@ -464,7 +469,7 @@ - (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer) // flip and convert to screen coordinates double viewHeight = self.flutterViewController.view.bounds.size.height; - rect.origin.y = viewHeight - rect.origin.y; + rect.origin.y = viewHeight - rect.origin.y - rect.size.height; return [self.flutterViewController.view.window convertRectToScreen:rect]; } else { return CGRectZero; From 1a95c5abea82ce58f4adbede659c49cd5e28da5f Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Wed, 14 Apr 2021 01:12:25 +0200 Subject: [PATCH 2/6] Use ivar to initialize flutterViewController --- .../darwin/macos/framework/Source/FlutterTextInputPlugin.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm index de7b7e1f7025d..2f4589532d2a3 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm @@ -166,7 +166,7 @@ - (instancetype)initWithViewController:(FlutterViewController*)viewController { _textInputContext = [[NSTextInputContext alloc] initWithClient:self]; _previouslyPressedFlags = 0; - self.flutterViewController = viewController; + _flutterViewController = viewController; // Initialize with the zero matrix which is not // an affine transform. From 18f584b537301a55d598fd8b496886bdd545e4dd Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Fri, 16 Apr 2021 23:27:00 +0200 Subject: [PATCH 3/6] Add test --- .../framework/Source/FlutterTextInputPlugin.h | 1 + .../Source/FlutterTextInputPluginTest.mm | 71 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h index 7ce919d22e0b8..39a98d319aff1 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h @@ -29,4 +29,5 @@ // Private methods made visible for testing @interface FlutterTextInputPlugin (TestMethods) - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result; +- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange; @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm index 5712b1615b60f..c97e42defc44b 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm @@ -81,12 +81,83 @@ - (bool)testEmptyCompositionRange { return true; } +- (bool)testFirstRectForCharacterRange { + id engineMock = OCMClassMock([FlutterEngine class]); + id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); + OCMStub( // NOLINT(google-objc-avoid-throwing-exception) + [engineMock binaryMessenger]) + .andReturn(binaryMessengerMock); + id controllerMock = OCMClassMock([FlutterViewController class]); + OCMStub( // NOLINT(google-objc-avoid-throwing-exception) + [controllerMock engine]) + .andReturn(engineMock); + + id viewMock = OCMClassMock([NSView class]); + OCMStub( // NOLINT(google-objc-avoid-throwing-exception) + [viewMock bounds]) + .andReturn(NSMakeRect(0, 0, 200, 200)); + OCMStub( // NOLINT(google-objc-avoid-throwing-exception) + [controllerMock view]) + .andReturn(viewMock); + + id windowMock = OCMClassMock([NSWindow class]); + OCMStub( // NOLINT(google-objc-avoid-throwing-exception) + [viewMock window]) + .andReturn(windowMock); + + OCMExpect( // NOLINT(google-objc-avoid-throwing-exception) + [windowMock convertRectToScreen:NSMakeRect(28, 171, 2, 19)]) + .andReturn(NSMakeRect(38, 181, 2, 19)); + + FlutterTextInputPlugin* plugin = + [[FlutterTextInputPlugin alloc] initWithViewController:controllerMock]; + + FlutterMethodCall* call = [FlutterMethodCall + methodCallWithMethodName:@"TextInput.setEditableSizeAndTransform" + arguments:@{ + @"height" : @(20.0), + @"transform" : @[ + @(1.0), @(0.0), @(0.0), @(0.0), @(0.0), @(1.0), @(0.0), @(0.0), @(0.0), + @(0.0), @(1.0), @(0.0), @(20.0), @(10.0), @(0.0), @(1.0) + ], + @"width" : @(400.0), + }]; + + [plugin handleMethodCall:call + result:^(id){ + }]; + + call = [FlutterMethodCall methodCallWithMethodName:@"TextInput.setCaretRect" + arguments:@{ + @"height" : @(19.0), + @"width" : @(2.0), + @"x" : @(8.0), + @"y" : @(0.0), + }]; + + [plugin handleMethodCall:call + result:^(id){ + }]; + + NSRect rect = [plugin firstRectForCharacterRange:NSMakeRange(0, 0) actualRange:nullptr]; + + @try { + OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) + [windowMock convertRectToScreen:NSMakeRect(28, 171, 2, 19)]); + } @catch (...) { + return false; + } + + return NSEqualRects(rect, NSMakeRect(38, 181, 2, 19)); +} + @end namespace flutter::testing { TEST(FlutterTextInputPluginTest, TestEmptyCompositionRange) { ASSERT_TRUE([[FlutterInputPluginTestObjc alloc] testEmptyCompositionRange]); + ASSERT_TRUE([[FlutterInputPluginTestObjc alloc] testFirstRectForCharacterRange]); } } // namespace flutter::testing From c2fa7f10acaa20f9200ad195da967e698d17417c Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Thu, 22 Apr 2021 23:22:45 +0200 Subject: [PATCH 4/6] Convert rect from view to window coordinates --- .../macos/framework/Source/FlutterTextInputPlugin.mm | 7 ++++++- .../macos/framework/Source/FlutterTextInputPluginTest.mm | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm index 2f4589532d2a3..2517e30437f75 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm @@ -467,9 +467,14 @@ - (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer) CGRect rect = CGRectApplyAffineTransform(_caretRect, CATransform3DGetAffineTransform(_editableTransform)); - // flip and convert to screen coordinates + // flip double viewHeight = self.flutterViewController.view.bounds.size.height; rect.origin.y = viewHeight - rect.origin.y - rect.size.height; + + // convert to window coordinates + rect = [self.flutterViewController.view convertRect:rect toView:nil]; + + // convert to screen coordinates return [self.flutterViewController.view.window convertRectToScreen:rect]; } else { return CGRectZero; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm index c97e42defc44b..cf07ecb840566 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm @@ -105,6 +105,10 @@ - (bool)testFirstRectForCharacterRange { [viewMock window]) .andReturn(windowMock); + OCMExpect( // NOLINT(google-objc-avoid-throwing-exception) + [viewMock convertRect:NSMakeRect(28, 171, 2, 19) toView:nil]) + .andReturn(NSMakeRect(28, 171, 2, 19)); + OCMExpect( // NOLINT(google-objc-avoid-throwing-exception) [windowMock convertRectToScreen:NSMakeRect(28, 171, 2, 19)]) .andReturn(NSMakeRect(38, 181, 2, 19)); From a55f91c7b62243582e50ab2e2290eb3616e4c997 Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Thu, 22 Apr 2021 23:23:01 +0200 Subject: [PATCH 5/6] Separate tests --- .../macos/framework/Source/FlutterTextInputPluginTest.mm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm index cf07ecb840566..0614b45d24a88 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm @@ -161,6 +161,9 @@ - (bool)testFirstRectForCharacterRange { TEST(FlutterTextInputPluginTest, TestEmptyCompositionRange) { ASSERT_TRUE([[FlutterInputPluginTestObjc alloc] testEmptyCompositionRange]); +} + +TEST(FlutterTextInputPluginTest, TestFirstRectForCharacterRange) { ASSERT_TRUE([[FlutterInputPluginTestObjc alloc] testFirstRectForCharacterRange]); } From b0c1a09449ff19af9b312a4e7237b096dc13111d Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Fri, 23 Apr 2021 00:03:58 +0200 Subject: [PATCH 6/6] Don't flip the rect, FlutterView overrides isFlipped. --- .../macos/framework/Source/FlutterTextInputPlugin.mm | 4 ---- .../framework/Source/FlutterTextInputPluginTest.mm | 12 ++++++------ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm index 2517e30437f75..75fba93c4caa5 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm @@ -467,10 +467,6 @@ - (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer) CGRect rect = CGRectApplyAffineTransform(_caretRect, CATransform3DGetAffineTransform(_editableTransform)); - // flip - double viewHeight = self.flutterViewController.view.bounds.size.height; - rect.origin.y = viewHeight - rect.origin.y - rect.size.height; - // convert to window coordinates rect = [self.flutterViewController.view convertRect:rect toView:nil]; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm index 0614b45d24a88..0eeb2d6bc7d72 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm @@ -106,12 +106,12 @@ - (bool)testFirstRectForCharacterRange { .andReturn(windowMock); OCMExpect( // NOLINT(google-objc-avoid-throwing-exception) - [viewMock convertRect:NSMakeRect(28, 171, 2, 19) toView:nil]) - .andReturn(NSMakeRect(28, 171, 2, 19)); + [viewMock convertRect:NSMakeRect(28, 10, 2, 19) toView:nil]) + .andReturn(NSMakeRect(28, 10, 2, 19)); OCMExpect( // NOLINT(google-objc-avoid-throwing-exception) - [windowMock convertRectToScreen:NSMakeRect(28, 171, 2, 19)]) - .andReturn(NSMakeRect(38, 181, 2, 19)); + [windowMock convertRectToScreen:NSMakeRect(28, 10, 2, 19)]) + .andReturn(NSMakeRect(38, 20, 2, 19)); FlutterTextInputPlugin* plugin = [[FlutterTextInputPlugin alloc] initWithViewController:controllerMock]; @@ -147,12 +147,12 @@ - (bool)testFirstRectForCharacterRange { @try { OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) - [windowMock convertRectToScreen:NSMakeRect(28, 171, 2, 19)]); + [windowMock convertRectToScreen:NSMakeRect(28, 10, 2, 19)]); } @catch (...) { return false; } - return NSEqualRects(rect, NSMakeRect(38, 181, 2, 19)); + return NSEqualRects(rect, NSMakeRect(38, 20, 2, 19)); } @end