From ccfb382685f32693fe6042d39b496ab6469d819f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 May 2024 20:05:22 -0700 Subject: [PATCH] Cherry Pick: [SuperEditor][Mobile] Fix downstream image caret position (Resolves #1959) (#1995) [SuperEditor][Mobile] Fix downstream image caret position (Resolves #1959) (#1967) Co-authored-by: Angelo Silvestre --- .../android/android_document_controls.dart | 20 +++++- .../platforms/ios/ios_document_controls.dart | 20 +++++- super_editor/pubspec.yaml | 2 +- ...-editor-image-caret-downstream-android.png | Bin 0 -> 21717 bytes ...uper-editor-image-caret-downstream-ios.png | Bin 0 -> 20979 bytes ...per-editor-image-caret-downstream-mac.png} | Bin 20980 -> 20980 bytes ...er-editor-image-caret-upstream-android.png | Bin 0 -> 21829 bytes .../super-editor-image-caret-upstream-ios.png | Bin 0 -> 20981 bytes ...super-editor-image-caret-upstream-mac.png} | Bin .../editor/supereditor_caret_test.dart | 66 +++++++++++++++++- 10 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 super_editor/test_goldens/editor/goldens/super-editor-image-caret-downstream-android.png create mode 100644 super_editor/test_goldens/editor/goldens/super-editor-image-caret-downstream-ios.png rename super_editor/test_goldens/editor/goldens/{super-editor-image-caret-downstream.png => super-editor-image-caret-downstream-mac.png} (98%) create mode 100644 super_editor/test_goldens/editor/goldens/super-editor-image-caret-upstream-android.png create mode 100644 super_editor/test_goldens/editor/goldens/super-editor-image-caret-upstream-ios.png rename super_editor/test_goldens/editor/goldens/{super-editor-image-caret-upstream.png => super-editor-image-caret-upstream-mac.png} (100%) diff --git a/super_editor/lib/src/infrastructure/platforms/android/android_document_controls.dart b/super_editor/lib/src/infrastructure/platforms/android/android_document_controls.dart index b0bc0ae78..2c451d41e 100644 --- a/super_editor/lib/src/infrastructure/platforms/android/android_document_controls.dart +++ b/super_editor/lib/src/infrastructure/platforms/android/android_document_controls.dart @@ -313,8 +313,26 @@ class AndroidControlsDocumentLayerState } if (selection.isCollapsed && !_controlsController!.shouldShowExpandedHandles.value) { + Rect caretRect = documentLayout.getEdgeForPosition(selection.extent)!; + + // Default caret width used by the Android caret. + const caretWidth = 2; + + final layerBox = context.findRenderObject() as RenderBox?; + if (layerBox != null && layerBox.hasSize && caretRect.left + caretWidth >= layerBox.size.width) { + // Ajust the caret position to make it entirely visible because it's currently placed + // partially or entirely outside of the layers' bounds. This can happen for downstream selections + // of block components that take all the available width. + caretRect = Rect.fromLTWH( + layerBox.size.width - caretWidth, + caretRect.top, + caretRect.width, + caretRect.height, + ); + } + return DocumentSelectionLayout( - caret: documentLayout.getRectForPosition(selection.extent)!, + caret: caretRect, ); } else { return DocumentSelectionLayout( diff --git a/super_editor/lib/src/infrastructure/platforms/ios/ios_document_controls.dart b/super_editor/lib/src/infrastructure/platforms/ios/ios_document_controls.dart index 7a09b6bb0..59ab129bb 100644 --- a/super_editor/lib/src/infrastructure/platforms/ios/ios_document_controls.dart +++ b/super_editor/lib/src/infrastructure/platforms/ios/ios_document_controls.dart @@ -675,8 +675,26 @@ class IosControlsDocumentLayerState extends DocumentLayoutLayerState= layerBox.size.width) { + // Ajust the caret position to make it entirely visible because it's currently placed + // partially or entirely outside of the layers' bounds. This can happen for downstream selections + // of block components that take all the available width. + caretRect = Rect.fromLTWH( + layerBox.size.width - caretWidth, + caretRect.top, + caretRect.width, + caretRect.height, + ); + } + return DocumentSelectionLayout( - caret: documentLayout.getRectForPosition(selection.extent)!, + caret: caretRect, ); } else { return DocumentSelectionLayout( diff --git a/super_editor/pubspec.yaml b/super_editor/pubspec.yaml index 22fade5ad..ab841e570 100644 --- a/super_editor/pubspec.yaml +++ b/super_editor/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: attributed_text: ^0.3.0 characters: ^1.2.0 collection: ^1.15.0 - follow_the_leader: ^0.0.4+7 + follow_the_leader: ^0.0.4+8 http: ">=0.13.1 <2.0.0" linkify: ^5.0.0 logging: ^1.0.1 diff --git a/super_editor/test_goldens/editor/goldens/super-editor-image-caret-downstream-android.png b/super_editor/test_goldens/editor/goldens/super-editor-image-caret-downstream-android.png new file mode 100644 index 0000000000000000000000000000000000000000..c7bbd55404196d4085bcd719bb7faa25d99abd1e GIT binary patch literal 21717 zcmeHPYfw{16uu}TND&_uDUVT*kx_8O2g56Alu|LGVAT#mB`^at4V8xmK?FmlzM!Jm zT8pTlEsm`N6F{qoXh2&D76>9Qfe_S)fCMfP2|->B$qjqc|5j_y{gb^r_nzJJ?RUQ2 zv$HoTn|-}aW?0O?FwBI$-fIhn83kk5bVEY}Rn5)%H~Oj{JQde4b3EzW z3{_X6VQ4ajEyUY!@5u zslED(+4aZ(kAvY`iz;Jn0&HWm%{(tLj;C`Rmn`+MUREc~kgsHwt>X9|OJ0BeVaNIY z;epXiVaQN%Vh~Py@*s{qTx1$)s_KL05_NMf42^Vi1(bdtZ5?KI2s75+ESwXpr!9L; zanu%h>v_|(P%A7ssk$aGz zE(_Idv!GU9KZ|7GZlsO@$6Kx6LW(cJp!Y1Mnl&plEo%pHVi{IRI!3*&vViM}^lqUusRs$AK8+mAFzwK01Rgh=cW zk$7zP0&ci&%_{@65bq>y1Y8VA=xvX`un@s;nay^r8rCPlwdrWy%}`x)?lDbcx^Hzo zX(w_2`9m9IV9hfVwDhaFj=NIuxg?KP-{~%IDI4=qR1FxQmz?Mo6+Y7PXf3uPvTSX?lc zB}T5SW3|!9?8|qO#P#o$F7i&R)Zy@t*@a?PeiB}qBrPl!Vd z*I~(?8s)w8PghI0txp461Dcy3OCPzFKeo&*NbuSnP`jiYeMcuw#?YOiu z<62Z_5;DwdWE|-1k~h+*G~cYp=F2lFRA&58rE8I31#`drwRuRYk13NfGw=o}m^Lq~ z9@Y{D<6R-?2kdph79hcl_hgrD>;d0eJtiN$D$M$$_nF~D_s-}ewWcF!hHA~&@c>jRfdYYgPXr1?2}J2# zPm;hv;NZW`!Q?;>^*J^3=TX$7t@mfEhG65A$&+iPc4*rlsx|ZkOf)zEPY4`<5I_h} zO@ad$L<2+vL<2;F3IUJ;kOGhbkOGhb$fDIz09yzeF@TAY5XJu)A!ZfFs*kiemACDm zS$u2}hAq~cJc<}RXYyn!R1^FFOb8%8Af5z$k%wd0$E=C}1(@*e19A=A0Xb6#Ea;XR z1_*8f1`G%Rgn(#(Xiy;lQUFo_QUFo_QULoM>L`E&SR)2+abmm}+oS&YKKNwGfK}{# yZ|oHMFx$xpeU}f_nxOzpG&lfH2ps;igqZd!DQszU)wLfc9HD>i>&5pB{`PNSITY#u literal 0 HcmV?d00001 diff --git a/super_editor/test_goldens/editor/goldens/super-editor-image-caret-downstream-ios.png b/super_editor/test_goldens/editor/goldens/super-editor-image-caret-downstream-ios.png new file mode 100644 index 0000000000000000000000000000000000000000..a4903593e859f44e31c158a8583fa23fc608f765 GIT binary patch literal 20979 zcmeI4KWGzS7{=cV(IyS4ZG{RYL(t+R5(fuyNkU3;C`D=qhgJ@BmO>yD5qf9~l|uao zQo%*siyah85Hm?d5ghzO5ITs0Hq}90DirVXyvlE zDW|&!yG11JR!XBHNl&CJk=PrIJbkwr4?djgXt^j$Z!+J4lX$i0jwOQQO5)<2NLJiZ zVQlJt>(g@jTE2In zDLWurGm^3=*?!Nlbt!hlmVU+GW9#1JBU{QrXTa9^p+?NsbMbSwlv{kc#R6Dhi*f-G z;Vnu9ESF}XRG^0vEtCo*E`q31fe5`@W)`3S-uw08kiG5lW3`Kvq-c14gucKO`u+U1`5d|>~J@yPbwsP{2!?z#^q1_%fNAp|vOFF*~b0X3)?$Pfwy z_J}=pcA_49^&)520x(%ayquX zKB+Mj{r$+WaZxbQZUP}7grEl0AhZl60ct=Es6jbEZ?Hg+lnfHsBld_rT30{~r~x(X zE;Zy^&E@bHH?q^~Ct61r4m;cLoO=DydPar~Ex<(O0U;oSpoafcL+kD4YmvU$a9;oc zHbqS^5ljR%poRbetN<&(3a|pKfcbZ*0;U$~1vEwjMCTF0Z$7>dZtCV2w%%7iPGsx` sZ@FajO8K2=e>a9z5ny6~fDk((#MzAp1LN~|7hi@~yTc=;rJ{HBA03}dj{pDw literal 0 HcmV?d00001 diff --git a/super_editor/test_goldens/editor/goldens/super-editor-image-caret-downstream.png b/super_editor/test_goldens/editor/goldens/super-editor-image-caret-downstream-mac.png similarity index 98% rename from super_editor/test_goldens/editor/goldens/super-editor-image-caret-downstream.png rename to super_editor/test_goldens/editor/goldens/super-editor-image-caret-downstream-mac.png index 6bcbc7ba38f7f76e34588d5fb0c7935dfc534da0..7e1171d8d74f15737f3ff5385a330442c8997e3a 100644 GIT binary patch delta 46 xcmeyenDNVE#tF5&HJl7kaA3Z`=7o#}JR28?Gj0~-GZkW4`Fqou&0Y$t3;|+a53m3L delta 46 xcmeyenDNVE#tF5&73>U9aNxec=7o#}JR28?Gj0~-GZkXVdM@j{*-K%SApl+D4tW3o diff --git a/super_editor/test_goldens/editor/goldens/super-editor-image-caret-upstream-android.png b/super_editor/test_goldens/editor/goldens/super-editor-image-caret-upstream-android.png new file mode 100644 index 0000000000000000000000000000000000000000..55a1c0de87833d0c1938208c8e661bf0259d1f09 GIT binary patch literal 21829 zcmeHPX;4#F7`+h+5rnW5R7OOUYDYnsBBQKQL@_GMs30O>5JpB;1%eulW-KjG)Ydu` zM8Hu5H3=atAe-Q8WpM*+kpNW?QHc;1SwhMpG(g@<^rwGt>i7Qe-gn=<-@WIZb8g>SpKd2+gG-q)H~MNFzlJqsr1hr0DG~PKZ~#s8f2O9PLEiNtPbR z$w3JSEkRW0Z+9L^>u#w@N)I-ye)0U`qo8l`cWNn5Uv{fZ9lPX4}R$I2qDE1Z&k4bzgZ$>pZbXKtx$hiyHhW}Hr~-yfzcEJ{3; zlGrJ+6BY}Dc%jx75{X3h_`15bbLZVDBDM07oa?-yU}=|Tj@6e!nD{xuuIbQ7_*I;; zH-3tYN&5J?<7UoBO`P zUyYFN{<}zYe}&tRd?Li#Ck=Hs`z>7x)&QQ%nNS4k!wG3o)J#ZDNVwgL(3~Rlf3-S@-xP}+&2>4z%E!uu{=HL z9eqN@U_DhaHBG(CRB>$l$`}L1t~NQ@Q21Noh>nD52hM%(niQzc?}~~Oi&tcDw$5*? zwpPNs^|-=YiOXj1^7pyfXD3cNREHO!p@?phmuExn$&o1gu`=OU${f7tjjQ=o2_?hY z(|o))msvw#c-k+LBsl0T?ui_~Em-Z_9jb%Zp>Y}Nd^WrF$h#cI@R-9!GG5?o#L$oy zWQ_G2g}!c3XVwvEVt5iHYOg#gpj@_YkPr{vQOKZ3E1VGL%@+-=;+SO<*=_1gFayna zac18{dF2{Bfn#v0vbU#a-Q;jgnYbg%YY;C$%R=>OekJ+-=4qpQI7jg?>zvh8Pbc19 zx|5%#iboevUm{VAM!cCh9zpl4H2D7OC-;~Ad)?B^od_pL!)&(y#2_a}A-IOZ_u92O z-+}1tlE3XRo5J4((+eKcm01ahAm5TFc9@Tzd__T*eUwFyckBzbwB*P=z@iUr7z+&vc|0(+elvZryz7zZUeDS>MG*5)D1*V)G;DbqvKYUS7F08b zd$(HB@Mt(Zr-J}!lfV=kea3Y0aV5wabCt@Xw`cZo%EspVY?FtxB`Eait(zsnD}f*K zC-Cy^0)2hqSOy`EH@>+fL3eiybGUpN(~TI!X;*1jXjGON(NtVkN5wnG`mNIp8+}w3 zFKSB6D(}F^tgNhV*gBlp+9JlHdi+yF?eg)sM!rH*W08WDNv|(G zOpn&a6Jl`*N?eL*Urwo!FE2|esl}gom=?x7Yb-TAo%i&@^BoV3p6zvsxKrHG5nOd_ zAyIqzT4r0@8F~agr}ob9PX_Z;3Pgr^1;+w-9fxZLycXM#fI45pbw>gX9m;dXIe0a6 z=>d%yzAlVO|DtLy|UyaNgD9pIq+b&ygXHn(fW~3++E8q~8JuJXv1_&So5CS1k z1E>Lk6@V3h705UMYQTYjxdG+|Py?tziU3#vSOHi8Si$FO1qpt()4S>yyo$7sZ>gNw z9*UXBHzRMGBj0ro;Tfp_CNnt90#C?)JopQtFB_+R7ho3BLrU3X0TP%s2>=Iyg9w2D zL4XLX0IUG4K*j-30}cet4KO!=8bA$F1i%VDQ7iB$jh)`aW^wO1L$fq#*;F@aI>dkP sqva&x_W>Y0^9%tK84f^*|0f}`QM8t-(-RltMN{LWZgg|zInhr22Q&^#l>h($ literal 0 HcmV?d00001 diff --git a/super_editor/test_goldens/editor/goldens/super-editor-image-caret-upstream-ios.png b/super_editor/test_goldens/editor/goldens/super-editor-image-caret-upstream-ios.png new file mode 100644 index 0000000000000000000000000000000000000000..ba75df7e02bd5ded33d4542f19b4f2b39defd5de GIT binary patch literal 20981 zcmeI4O-K}B7{{MoHk;kta1FE~LuwKHpghzm0$oOQKt#kt5XF|>MFbU*Sn}XX5J(pf zSrp+VSUdy@UIaBdX%Nw&M6?K_Fi`^al0>S=3CNpA7dFLx)>lXBWEcN?@ivm)i<*!AuG z&;NXT6`!jsYN&r%-CTODay0d+W9)kJ<9g!vn+wzHrJL`*_YeN;UH$jy{&>heinr~2 z{d-x*GFcTW(Ve>FY*@$H$Q>QZsGQMpd2#yiICi*3r!!5NQ#vk+PU=vuaB#%}SYV6c z0wTg&3>C0k!opC245hTtVu6Z_Ley}968eO67Vme*w>J{%08+gbqU$nf(2UZf8!FI7 z(?%OAc!l~nk5y% z zS!&4MyXC#qcK%1#?cwEx#Isn#|r{jf4{ xcIL@dJ@>Ilu#VUXKKsVB4^Re7d>|miu?SHa8!D-oxN+0Fx$QKx*tt~t@jg0F(WL+Y literal 0 HcmV?d00001 diff --git a/super_editor/test_goldens/editor/goldens/super-editor-image-caret-upstream.png b/super_editor/test_goldens/editor/goldens/super-editor-image-caret-upstream-mac.png similarity index 100% rename from super_editor/test_goldens/editor/goldens/super-editor-image-caret-upstream.png rename to super_editor/test_goldens/editor/goldens/super-editor-image-caret-upstream-mac.png diff --git a/super_editor/test_goldens/editor/supereditor_caret_test.dart b/super_editor/test_goldens/editor/supereditor_caret_test.dart index 7893c6794..165ad5c29 100644 --- a/super_editor/test_goldens/editor/supereditor_caret_test.dart +++ b/super_editor/test_goldens/editor/supereditor_caret_test.dart @@ -18,9 +18,40 @@ void main() { ); await tester.pump(); - await screenMatchesGolden(tester, 'super-editor-image-caret-downstream'); + await screenMatchesGolden(tester, 'super-editor-image-caret-downstream-mac'); }); + testGoldensOniOS('shows caret at right side of an image', (tester) async { + await _pumpCaretTestApp(tester); + + // Tap close to the right edge of the editor to place the caret + // downstream on the image. + await tester.tapAt( + tester.getTopRight(find.byType(SuperEditor)) + const Offset(-20, 20), + ); + await tester.pump(); + + await screenMatchesGolden(tester, 'super-editor-image-caret-downstream-ios'); + }); + + testGoldensOnAndroid( + 'shows caret at right side of an image', + (tester) async { + await _pumpCaretTestApp(tester); + + // Tap close to the right edge of the editor to place the caret + // downstream on the image. + await tester.tapAt( + tester.getTopRight(find.byType(SuperEditor)) + const Offset(-20, 20), + ); + await tester.pumpAndSettle(); + + await screenMatchesGolden(tester, 'super-editor-image-caret-downstream-android'); + }, + // TODO: find out why this test fails on CI only. + skip: true, + ); + testGoldensOnMac('shows caret at left side of an image', (tester) async { await _pumpCaretTestApp(tester); @@ -31,8 +62,39 @@ void main() { ); await tester.pump(); - await screenMatchesGolden(tester, 'super-editor-image-caret-upstream'); + await screenMatchesGolden(tester, 'super-editor-image-caret-upstream-mac'); }); + + testGoldensOniOS('shows caret at left side of an image', (tester) async { + await _pumpCaretTestApp(tester); + + // Tap close to the left edge of the editor to place the caret upstream + // on the image. + await tester.tapAt( + tester.getTopLeft(find.byType(SuperEditor)) + const Offset(20, 20), + ); + await tester.pump(); + + await screenMatchesGolden(tester, 'super-editor-image-caret-upstream-ios'); + }); + + testGoldensOnAndroid( + 'shows caret at left side of an image', + (tester) async { + await _pumpCaretTestApp(tester); + + // Tap close to the left edge of the editor to place the caret upstream + // on the image. + await tester.tapAt( + tester.getTopLeft(find.byType(SuperEditor)) + const Offset(20, 20), + ); + await tester.pump(); + + await screenMatchesGolden(tester, 'super-editor-image-caret-upstream-android'); + }, + // TODO: find out why this test fails on CI only. + skip: true, + ); }); }