1 /*
2 * Copyright (c) 2023-2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #include "core/components_ng/pattern/text_field/text_select_controller.h"
16
17 #include "base/geometry/ng/rect_t.h"
18 #include "base/geometry/offset.h"
19 #include "base/log/log_wrapper.h"
20 #include "base/utils/utf_helper.h"
21 #include "base/utils/utils.h"
22 #include "core/common/ai/data_detector_mgr.h"
23 #include "core/components_ng/pattern/text_field/text_field_pattern.h"
24 #include "core/components_ng/pattern/text_field/text_input_ai_checker.h"
25
26 namespace OHOS::Ace::NG {
27 namespace {
28 const std::string NEWLINE = "\n";
29 const std::u16string WIDE_NEWLINE = UtfUtils::Str8DebugToStr16(NEWLINE);
30 } // namespace
UpdateHandleIndex(int32_t firstHandleIndex,int32_t secondHandleIndex)31 void TextSelectController::UpdateHandleIndex(int32_t firstHandleIndex, int32_t secondHandleIndex)
32 {
33 firstHandleInfo_.index = firstHandleIndex;
34 secondHandleInfo_.index = secondHandleIndex;
35 caretInfo_.index = std::max(firstHandleInfo_.index, secondHandleInfo_.index);
36 CalculateHandleOffset();
37 UpdateCaretOffset();
38 }
39
UpdateCaretIndex(int32_t index)40 void TextSelectController::UpdateCaretIndex(int32_t index)
41 {
42 auto newIndex = std::clamp(index, 0, static_cast<int32_t>(contentController_->GetTextUtf16Value().length()));
43 caretInfo_.index = newIndex;
44 TAG_LOGD(AceLogTag::ACE_TEXT_FIELD, "newIndex change to %{public}d", newIndex);
45 firstHandleInfo_.index = newIndex;
46 secondHandleInfo_.index = newIndex;
47 }
48
CalculateEmptyValueCaretRect(float width)49 RectF TextSelectController::CalculateEmptyValueCaretRect(float width)
50 {
51 RectF rect;
52 auto pattern = pattern_.Upgrade();
53 CHECK_NULL_RETURN(pattern, rect);
54 auto textField = DynamicCast<TextFieldPattern>(pattern);
55 CHECK_NULL_RETURN(textField, rect);
56 auto layoutProperty = textField->GetLayoutProperty<TextFieldLayoutProperty>();
57 CHECK_NULL_RETURN(layoutProperty, rect);
58 rect.SetOffset(contentRect_.GetOffset());
59 rect.SetHeight(textField->PreferredLineHeight());
60 rect.SetWidth(GreatNotEqual(width, 0.0f) ? width : caretInfo_.rect.Width());
61 auto textAlign = layoutProperty->GetTextAlignValue(TextAlign::START);
62 auto direction = layoutProperty->GetNonAutoLayoutDirection();
63 textField->CheckTextAlignByDirection(textAlign, direction);
64
65 switch (textAlign) {
66 case TextAlign::START:
67 rect.SetLeft(contentRect_.GetX());
68 break;
69 case TextAlign::CENTER:
70 if (layoutProperty->GetPlaceholderValue(u"").empty() || !paragraph_) {
71 rect.SetLeft(static_cast<float>(contentRect_.GetX()) + contentRect_.Width() / 2.0f);
72 } else {
73 CaretMetricsF caretMetrics;
74 CalcCaretMetricsByPosition(0, caretMetrics, TextAffinity::DOWNSTREAM);
75 rect.SetLeft(caretMetrics.offset.GetX());
76 }
77 break;
78 case TextAlign::END:
79 rect.SetLeft(static_cast<float>(contentRect_.GetX()) + contentRect_.Width() -
80 static_cast<float>(rect.Width()));
81 break;
82 default:
83 break;
84 }
85
86 auto align = textField->IsTextArea() ? Alignment::TOP_CENTER : Alignment::CENTER;
87 if (layoutProperty->GetPositionProperty()) {
88 align = layoutProperty->GetPositionProperty()->GetAlignment().value_or(align);
89 }
90 OffsetF offset = Alignment::GetAlignPosition(contentRect_.GetSize(), rect.GetSize(), align);
91 rect.SetTop(offset.GetY() + contentRect_.GetY());
92 if (textAlign != TextAlign::END) {
93 AdjustHandleAtEdge(rect);
94 }
95 if (GreatNotEqual(rect.Height(), contentRect_.Height()) && !textField->IsTextArea()) {
96 rect.SetHeight(contentRect_.Height());
97 }
98 return rect;
99 }
100
FitCaretMetricsToTouchPoint(CaretMetricsF & caretMetrics,const Offset & touchOffset)101 void TextSelectController::FitCaretMetricsToTouchPoint(CaretMetricsF& caretMetrics, const Offset& touchOffset)
102 {
103 auto index = ConvertTouchOffsetToPosition(touchOffset);
104 // adust Y to align in one line center.
105 AdjustCursorPosition(index, touchOffset);
106 CalcCaretMetricsByPositionNearTouchOffset(index, caretMetrics,
107 OffsetF(static_cast<float>(touchOffset.GetX()), static_cast<float>(touchOffset.GetY())));
108
109 // ajust X to support display caret at anywhere
110 caretMetrics.offset.SetX(touchOffset.GetX());
111 }
112
FitCaretMetricsToContentRect(CaretMetricsF & caretMetrics)113 void TextSelectController::FitCaretMetricsToContentRect(CaretMetricsF& caretMetrics)
114 {
115 auto pattern = pattern_.Upgrade();
116 CHECK_NULL_VOID(pattern);
117 auto textField = DynamicCast<TextFieldPattern>(pattern);
118 CHECK_NULL_VOID(textField);
119 if (GreatNotEqual(caretMetrics.height, contentRect_.Height()) && !textField->IsTextArea()) {
120 caretMetrics.offset.SetY(caretMetrics.offset.GetY() + caretMetrics.height - contentRect_.Height());
121 caretMetrics.height = contentRect_.Height();
122 }
123 }
124
CalcCaretMetricsByPosition(int32_t extent,CaretMetricsF & caretCaretMetric,TextAffinity textAffinity)125 void TextSelectController::CalcCaretMetricsByPosition(
126 int32_t extent, CaretMetricsF& caretCaretMetric, TextAffinity textAffinity)
127 {
128 CHECK_NULL_VOID(paragraph_);
129 paragraph_->CalcCaretMetricsByPosition(extent, caretCaretMetric, textAffinity);
130 auto pattern = pattern_.Upgrade();
131 CHECK_NULL_VOID(pattern);
132 auto textField = DynamicCast<TextFieldPattern>(pattern);
133 CHECK_NULL_VOID(textField);
134 auto textRect = textField->GetTextRect();
135 caretCaretMetric.offset.AddX(textRect.GetX());
136 caretCaretMetric.offset.AddY(textRect.GetY());
137 FitCaretMetricsToContentRect(caretCaretMetric);
138 }
139
CalcCaretMetricsByPositionNearTouchOffset(int32_t extent,CaretMetricsF & caretMetrics,const OffsetF & touchOffset)140 void TextSelectController::CalcCaretMetricsByPositionNearTouchOffset(
141 int32_t extent, CaretMetricsF& caretMetrics, const OffsetF& touchOffset)
142 {
143 CHECK_NULL_VOID(paragraph_);
144 auto pattern = pattern_.Upgrade();
145 CHECK_NULL_VOID(pattern);
146 auto textField = DynamicCast<TextFieldPattern>(pattern);
147 CHECK_NULL_VOID(textField);
148 auto textRect = textField->GetTextRect();
149 paragraph_->CalcCaretMetricsByPosition(extent, caretMetrics, touchOffset - textRect.GetOffset(), textAffinity_);
150 caretMetrics.offset.AddX(textRect.GetX());
151 caretMetrics.offset.AddY(textRect.GetY());
152 FitCaretMetricsToContentRect(caretMetrics);
153 }
154
UpdateCaretRectByPositionNearTouchOffset(int32_t position,const Offset & touchOffset)155 void TextSelectController::UpdateCaretRectByPositionNearTouchOffset(int32_t position, const Offset& touchOffset)
156 {
157 CaretMetricsF caretMetrics;
158 CalcCaretMetricsByPositionNearTouchOffset(position, caretMetrics,
159 OffsetF(static_cast<float>(touchOffset.GetX()), static_cast<float>(touchOffset.GetY())));
160
161 caretInfo_.UpdateOffset(caretMetrics.offset);
162 UpdateCaretHeight(caretMetrics.height);
163 }
164
UpdateCaretInfoByOffset(const Offset & localOffset,bool moveContent,bool floatCaret)165 void TextSelectController::UpdateCaretInfoByOffset(const Offset& localOffset, bool moveContent, bool floatCaret)
166 {
167 auto index = ConvertTouchOffsetToPosition(localOffset);
168 AdjustCursorPosition(index, localOffset);
169 UpdateCaretIndex(index);
170 if (!contentController_->IsEmpty()) {
171 UpdateCaretRectByPositionNearTouchOffset(index, localOffset);
172 auto offset = caretInfo_.rect.GetOffset();
173 if (moveContent) {
174 MoveHandleToContentRect(caretInfo_.rect, 0.0f);
175 } else {
176 AdjustHandleAtEdge(caretInfo_.rect);
177 }
178 UpdateCaretOriginalRect(offset);
179 } else {
180 SetCaretRectAtEmptyValue();
181 }
182
183 CHECK_NULL_VOID(floatCaret);
184 auto pattern = pattern_.Upgrade();
185 CHECK_NULL_VOID(pattern);
186 auto textField = DynamicCast<TextFieldPattern>(pattern);
187 CHECK_NULL_VOID(textField);
188 textField->AdjustFloatingCaretInfo(localOffset, caretInfo_, floatingCaretInfo_);
189 }
190
CalcCaretOffsetByOffset(const Offset & localOffset)191 OffsetF TextSelectController::CalcCaretOffsetByOffset(const Offset& localOffset)
192 {
193 auto index = ConvertTouchOffsetToPosition(localOffset);
194 AdjustCursorPosition(index, localOffset);
195 if (!contentController_->IsEmpty()) {
196 CaretMetricsF caretMetrics;
197 CalcCaretMetricsByPositionNearTouchOffset(index, caretMetrics,
198 OffsetF(static_cast<float>(localOffset.GetX()), static_cast<float>(localOffset.GetY())));
199 return caretMetrics.offset;
200 } else {
201 return CalculateEmptyValueCaretRect().GetOffset();
202 }
203 }
204
ConvertTouchOffsetToPosition(const Offset & localOffset,bool isSelectionPos) const205 int32_t TextSelectController::ConvertTouchOffsetToPosition(const Offset& localOffset, bool isSelectionPos) const
206 {
207 CHECK_NULL_RETURN(paragraph_, 0);
208 if (contentController_->IsEmpty()) {
209 return 0;
210 }
211 auto pattern = pattern_.Upgrade();
212 CHECK_NULL_RETURN(pattern, 0);
213 auto textField = DynamicCast<TextFieldPattern>(pattern);
214 CHECK_NULL_RETURN(textField, 0);
215 auto textRect = textField->GetTextRect();
216 auto offset = localOffset - Offset(textRect.GetX(), textRect.GetY());
217 return paragraph_->GetGlyphIndexByCoordinate(offset, isSelectionPos);
218 }
219
UpdateSelectByOffset(const Offset & localOffset)220 void TextSelectController::UpdateSelectByOffset(const Offset& localOffset)
221 {
222 CHECK_NULL_VOID(paragraph_ && !contentController_->IsEmpty());
223 auto pattern = pattern_.Upgrade();
224 CHECK_NULL_VOID(pattern);
225 auto textField = DynamicCast<TextFieldPattern>(pattern);
226 CHECK_NULL_VOID(textField);
227 auto textRect = textField->GetTextRect();
228 auto contentRect = textField->GetTextContentRect();
229 auto touchLocalOffset = localOffset;
230 if (textField->IsTextArea() && GreatNotEqual(touchLocalOffset.GetY(), textRect.Bottom())) {
231 // click at end of a paragraph.
232 touchLocalOffset.SetX(textField->IsLTRLayout() ? contentRect.Right() : textRect.Left());
233 }
234
235 auto range = GetSelectRangeByOffset(touchLocalOffset);
236 int32_t start = range.first;
237 int32_t end = range.second;
238 UpdateHandleIndex(start, end);
239 if (IsSelected()) {
240 MoveFirstHandleToContentRect(GetFirstHandleIndex(), false);
241 MoveSecondHandleToContentRect(GetSecondHandleIndex(), false);
242 } else {
243 MoveCaretToContentRect(GetCaretIndex());
244 }
245 }
246
UpdateSelectPragraphByOffset(const Offset & localOffset)247 void TextSelectController::UpdateSelectPragraphByOffset(const Offset& localOffset)
248 {
249 CHECK_NULL_VOID(paragraph_ && !contentController_->IsEmpty());
250
251 auto range = GetSelectParagraphByOffset(localOffset);
252 int32_t start = range.first;
253 int32_t end = range.second;
254 UpdateHandleIndex(start, end);
255 if (IsSelected()) {
256 MoveFirstHandleToContentRect(GetFirstHandleIndex());
257 MoveSecondHandleToContentRect(GetSecondHandleIndex());
258 } else {
259 MoveCaretToContentRect(GetCaretIndex());
260 }
261 }
262
GetSelectRangeByOffset(const Offset & localOffset)263 std::pair<int32_t, int32_t> TextSelectController::GetSelectRangeByOffset(const Offset& localOffset)
264 {
265 std::pair<int32_t, int32_t> err(-1, -1);
266 CHECK_NULL_RETURN(paragraph_ && !contentController_->IsEmpty(), err);
267 int32_t start = 0;
268 int32_t end = 0;
269 auto pos = ConvertTouchOffsetToPosition(localOffset, true);
270 // Ensure that the end is selected.
271 if (pos >= static_cast<int32_t>(paragraph_->GetParagraphText().length())) {
272 pos -= 1;
273 }
274
275 auto pattern = pattern_.Upgrade();
276 CHECK_NULL_RETURN(pattern, err);
277 auto textField = DynamicCast<TextFieldPattern>(pattern);
278 CHECK_NULL_RETURN(textField, err);
279 bool smartSelect = false;
280 if (!textField->IsUsingMouse()) {
281 smartSelect = AdjustWordSelection(pos, start, end, localOffset);
282 }
283
284 if (!smartSelect && !paragraph_->GetWordBoundary(pos, start, end)) {
285 start = pos;
286 end = std::min(static_cast<int32_t>(contentController_->GetTextUtf16Value().length()),
287 pos + GetGraphemeClusterLength(contentController_->GetTextUtf16Value(), pos, true));
288 }
289 if (SystemProperties::GetDebugEnabled()) {
290 TAG_LOGD(AceLogTag::ACE_TEXT,
291 "current word position = %{public}d, select position {start:%{public}d, end:%{public}d}", pos, start, end);
292 }
293 return { start, end };
294 }
295
GetSelectParagraphByOffset(const Offset & localOffset)296 std::pair<int32_t, int32_t> TextSelectController::GetSelectParagraphByOffset(const Offset& localOffset)
297 {
298 std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter;
299 std::pair<int32_t, int32_t> err(-1, -1);
300 CHECK_NULL_RETURN(paragraph_ && !contentController_->IsEmpty(), err);
301 int32_t start = 0;
302 int32_t end = 0;
303 auto pos = ConvertTouchOffsetToPosition(localOffset, true);
304 // Ensure that the end is selected.
305 if (pos >= static_cast<int32_t>(paragraph_->GetParagraphText().length())) {
306 pos -= 1;
307 }
308
309 auto pattern = pattern_.Upgrade();
310 CHECK_NULL_RETURN(pattern, err);
311 auto textField = DynamicCast<TextFieldPattern>(pattern);
312 CHECK_NULL_RETURN(textField, err);
313 if (!textField->IsUsingMouse()) {
314 AdjustWordSelection(pos, start, end, localOffset);
315 }
316
317 GetSubParagraphByOffset(pos, start, end);
318
319 if (SystemProperties::GetDebugEnabled()) {
320 TAG_LOGD(AceLogTag::ACE_TEXT,
321 "current word position = %{public}d, select position {start:%{public}d, end:%{public}d}", pos, start, end);
322 }
323 return { start, end };
324 }
325
GetSubParagraphByOffset(int32_t pos,int32_t & start,int32_t & end)326 void TextSelectController::GetSubParagraphByOffset(int32_t pos, int32_t &start, int32_t &end)
327 {
328 auto data = contentController_->GetTextUtf16Value();
329 auto dataLen = static_cast<int32_t>(data.length());
330 bool leftContinue = true;
331 bool rightContinue = true;
332 int32_t offset = 0;
333 if (pos <= 0) {
334 leftContinue = false;
335 start = 0;
336 }
337 while (leftContinue || rightContinue) {
338 if (leftContinue) {
339 if (pos - offset < 0 || pos - offset >= dataLen ||
340 data[pos - offset] == WIDE_NEWLINE[0]) {
341 start = pos - offset + 1;
342 leftContinue = false;
343 }
344 }
345 if (rightContinue) {
346 if (pos + offset >= dataLen || data[pos + offset] == WIDE_NEWLINE[0]) {
347 end = pos + offset;
348 rightContinue = false;
349 }
350 }
351 ++offset;
352 }
353 }
354
GetGraphemeClusterLength(const std::u16string & text,int32_t extend,bool checkPrev)355 int32_t TextSelectController::GetGraphemeClusterLength(const std::u16string& text, int32_t extend, bool checkPrev)
356 {
357 char16_t aroundChar = 0;
358 if (static_cast<size_t>(extend) <= text.length()) {
359 if (checkPrev) {
360 aroundChar = text[std::max(0, extend - 1)];
361 } else {
362 aroundChar = text[std::min(static_cast<int32_t>(text.length() - 1), extend)];
363 }
364 }
365 return StringUtils::NotInUtf16Bmp(aroundChar) ? 2 : 1;
366 }
367
CalculateHandleOffset()368 void TextSelectController::CalculateHandleOffset()
369 {
370 // calculate firstHandleOffset, secondHandleOffset and handlePaintSize
371 if (contentController_->IsEmpty()) {
372 SetCaretRectAtEmptyValue();
373 return;
374 }
375 CaretMetricsF secondHandleMetrics;
376 CalcCaretMetricsByPosition(GetSecondHandleIndex(), secondHandleMetrics, TextAffinity::UPSTREAM);
377 RectF secondHandle;
378 secondHandle.SetOffset(secondHandleMetrics.offset);
379 secondHandle.SetSize({ SelectHandleInfo::GetDefaultLineWidth().ConvertToPx(), secondHandleMetrics.height });
380 secondHandle.SetHeight(secondHandleMetrics.height);
381 AdjustHandleOffset(secondHandle);
382 secondHandleInfo_.rect = secondHandle;
383
384 if (!IsSelected()) {
385 return;
386 }
387
388 CaretMetricsF firstHandleMetrics;
389 CalcCaretMetricsByPosition(GetFirstHandleIndex(), firstHandleMetrics, TextAffinity::DOWNSTREAM);
390 OffsetF firstHandleOffset = firstHandleMetrics.offset;
391
392 RectF firstHandle;
393 firstHandle.SetOffset(firstHandleOffset);
394 firstHandle.SetSize({ SelectHandleInfo::GetDefaultLineWidth().ConvertToPx(), firstHandleMetrics.height });
395 AdjustHandleOffset(firstHandle);
396 firstHandleInfo_.rect = firstHandle;
397 }
398
ToString() const399 std::string TextSelectController::ToString() const
400 {
401 std::string result;
402 result.append("first handle offset: ");
403 result.append(std::to_string(firstHandleInfo_.index));
404 result.append(", second handle offset: ");
405 result.append(std::to_string(secondHandleInfo_.index));
406 return result;
407 }
408
GetSelectedRects() const409 std::vector<RectF> TextSelectController::GetSelectedRects() const
410 {
411 if (!IsSelected()) {
412 return {};
413 }
414 std::vector<RectF> selectedRects;
415 CHECK_NULL_RETURN(paragraph_, selectedRects);
416 paragraph_->GetRectsForRange(GetStartIndex(), GetEndIndex(), selectedRects);
417 return selectedRects;
418 }
419
MoveHandleToContentRect(RectF & handleRect,float boundaryAdjustment) const420 void TextSelectController::MoveHandleToContentRect(RectF& handleRect, float boundaryAdjustment) const
421 {
422 auto pattern = pattern_.Upgrade();
423 CHECK_NULL_VOID(pattern);
424 auto textField = DynamicCast<TextFieldPattern>(pattern);
425 CHECK_NULL_VOID(textField);
426 auto textRect = textField->GetTextRect();
427 if (GreatNotEqual(textRect.Height(), contentRect_.Height())) {
428 auto contentBottomBoundary = contentRect_.GetY() + contentRect_.Height();
429 if (LessNotEqual(handleRect.GetY(), contentRect_.GetY()) &&
430 LessOrEqual(handleRect.Height(), contentRect_.Height())) {
431 auto dy = contentRect_.GetY() - handleRect.GetY();
432 textRect.SetTop(textRect.GetY() + dy);
433 handleRect.SetTop(handleRect.GetY() + dy);
434 } else if (GreatNotEqual(handleRect.GetY() + handleRect.Height(), contentBottomBoundary)) {
435 auto dy = handleRect.GetY() + handleRect.Height() - contentBottomBoundary;
436 textRect.SetTop(textRect.GetY() - dy);
437 handleRect.SetTop(handleRect.GetY() - dy);
438 } else if (LessNotEqual(handleRect.GetY() + handleRect.Height(),
439 contentRect_.GetY() + contentRect_.Height()) &&
440 GreatNotEqual(handleRect.Height(), contentRect_.Height())) {
441 auto dy = contentRect_.GetY() - handleRect.GetY();
442 textRect.SetTop(textRect.GetY() + dy);
443 handleRect.SetTop(handleRect.GetY() + dy);
444 }
445 }
446
447 if (GreatNotEqual(textRect.Width(), contentRect_.Width())) {
448 auto contentRightBoundary = contentRect_.GetX() + contentRect_.Width() - boundaryAdjustment;
449 if (LessNotEqual(handleRect.GetX(), contentRect_.GetX())) {
450 auto dx = contentRect_.GetX() - handleRect.GetX();
451 textRect.SetLeft(textRect.GetX() + dx);
452 handleRect.SetLeft(handleRect.GetX() + dx);
453 } else if (GreatNotEqual(handleRect.GetX(), contentRightBoundary)) {
454 auto dx = handleRect.GetX() - contentRightBoundary;
455 textRect.SetLeft(textRect.GetX() - dx);
456 handleRect.SetLeft(handleRect.GetX() - dx);
457 }
458 }
459 textField->SetTextRect(textRect);
460 AdjustHandleAtEdge(handleRect);
461 textField->UpdateScrollBarOffset();
462 TAG_LOGI(AceLogTag::ACE_TEXTINPUT, "after move, handleRect.GetX():%{public}f,handleRect.GetY():%{public}f",
463 handleRect.GetX(), handleRect.GetY());
464 }
465
AdjustHandleAtEdge(RectF & handleRect) const466 void TextSelectController::AdjustHandleAtEdge(RectF& handleRect) const
467 {
468 AdjustHandleOffset(handleRect);
469 AdjustHandleInBoundary(handleRect);
470 }
471
AdjustHandleOffset(RectF & handleRect) const472 void TextSelectController::AdjustHandleOffset(RectF& handleRect) const
473 {
474 handleRect.SetOffset(OffsetF(handleRect.GetX() - handleRect.Width() / 2.0f, handleRect.GetY()));
475 // The handle position does not extend beyond the left edge of the text.
476 auto pattern = pattern_.Upgrade();
477 CHECK_NULL_VOID(pattern);
478 auto textField = DynamicCast<TextFieldPattern>(pattern);
479 CHECK_NULL_VOID(textField);
480 auto textRect = textField->GetTextRect();
481 if (LessNotEqual(handleRect.GetX(), textRect.GetX())) {
482 handleRect.SetOffset(OffsetF(textRect.GetX(), handleRect.GetY()));
483 }
484 }
485
MoveFirstHandleToContentRect(int32_t index,bool moveHandle,bool moveContent)486 void TextSelectController::MoveFirstHandleToContentRect(int32_t index, bool moveHandle, bool moveContent)
487 {
488 CaretMetricsF firstHandleMetrics;
489 firstHandleInfo_.index = index;
490 CalcCaretMetricsByPosition(
491 GetFirstHandleIndex(), firstHandleMetrics, HasReverse() ? TextAffinity::UPSTREAM : TextAffinity::DOWNSTREAM);
492 OffsetF firstHandleOffset = firstHandleMetrics.offset;
493 RectF firstHandle;
494 firstHandle.SetOffset(firstHandleOffset);
495 firstHandle.SetSize({ SelectHandleInfo::GetDefaultLineWidth().ConvertToPx(), firstHandleMetrics.height });
496 if (moveContent) {
497 MoveHandleToContentRect(firstHandle);
498 } else {
499 AdjustHandleAtEdge(firstHandle);
500 }
501 firstHandleInfo_.rect = firstHandle;
502
503 caretInfo_.index = std::max(firstHandleInfo_.index, secondHandleInfo_.index);
504 UpdateCaretOffset(TextAffinity::DOWNSTREAM, moveHandle);
505 UpdateSecondHandleOffset();
506 }
507
MoveSecondHandleToContentRect(int32_t index,bool moveHandle,bool moveContent)508 void TextSelectController::MoveSecondHandleToContentRect(int32_t index, bool moveHandle, bool moveContent)
509 {
510 CaretMetricsF secondHandleMetrics;
511 secondHandleInfo_.index = index;
512 CalcCaretMetricsByPosition(
513 GetSecondHandleIndex(), secondHandleMetrics, HasReverse() ? TextAffinity::DOWNSTREAM : TextAffinity::UPSTREAM);
514 RectF secondHandle;
515 secondHandle.SetOffset(secondHandleMetrics.offset);
516 secondHandle.SetSize({ SelectHandleInfo::GetDefaultLineWidth().ConvertToPx(), secondHandleMetrics.height });
517 if (moveContent) {
518 MoveHandleToContentRect(secondHandle);
519 } else {
520 AdjustHandleAtEdge(secondHandle);
521 }
522 secondHandleInfo_.rect = secondHandle;
523
524 caretInfo_.index = std::max(firstHandleInfo_.index, secondHandleInfo_.index);
525 UpdateCaretOffset(TextAffinity::DOWNSTREAM, moveHandle);
526 UpdateFirstHandleOffset();
527 }
528
MoveCaretToContentRect(int32_t index,TextAffinity textAffinity,bool isEditorValueChanged,bool moveContent)529 void TextSelectController::MoveCaretToContentRect(
530 int32_t index, TextAffinity textAffinity, bool isEditorValueChanged, bool moveContent)
531 {
532 if (isEditorValueChanged) {
533 textAffinity_ = textAffinity;
534 }
535 index = std::clamp(index, 0, static_cast<int32_t>(contentController_->GetTextUtf16Value().length()));
536 CaretMetricsF caretMetrics;
537 caretInfo_.index = index;
538 firstHandleInfo_.index = index;
539 secondHandleInfo_.index = index;
540 if (contentController_->IsEmpty()) {
541 SetCaretRectAtEmptyValue();
542 return;
543 }
544 CalcCaretMetricsByPosition(GetCaretIndex(), caretMetrics, textAffinity_);
545 OffsetF caretOffset = caretMetrics.offset;
546 RectF caretRect;
547 caretRect.SetOffset(caretOffset);
548 auto pattern = pattern_.Upgrade();
549 CHECK_NULL_VOID(pattern);
550 auto textField = DynamicCast<TextFieldPattern>(pattern);
551 CHECK_NULL_VOID(textField);
552 caretRect.SetSize({ caretInfo_.rect.Width(),
553 LessOrEqual(caretMetrics.height, 0.0) ? textField->PreferredLineHeight() : caretMetrics.height });
554
555 // Adjusts one character width.
556 float boundaryAdjustment = 0.0f;
557 auto textRect = textField->GetTextRect();
558 if (isEditorValueChanged && GreatNotEqual(textRect.Width(), contentRect_.Width()) &&
559 GreatNotEqual(contentRect_.Width(), 0.0) &&
560 caretInfo_.index < static_cast<int32_t>(contentController_->GetTextUtf16Value().length()) && paragraph_) {
561 boundaryAdjustment = paragraph_->GetCharacterWidth(caretInfo_.index);
562 if (SystemProperties::GetDebugEnabled()) {
563 TAG_LOGD(AceLogTag::ACE_TEXT, "caretInfo_.index = %{public}d, boundaryAdjustment =%{public}f",
564 caretInfo_.index, boundaryAdjustment);
565 }
566 }
567 if (moveContent) {
568 MoveHandleToContentRect(caretRect, boundaryAdjustment);
569 } else {
570 AdjustHandleAtEdge(caretRect);
571 }
572 caretInfo_.rect = caretRect;
573 UpdateCaretOriginalRect(caretMetrics.offset);
574 }
575
MoveCaretAnywhere(const Offset & touchOffset)576 void TextSelectController::MoveCaretAnywhere(const Offset& touchOffset)
577 {
578 CaretMetricsF caretMetrics;
579 if (contentController_->IsEmpty()) {
580 SetCaretRectAtEmptyValue();
581 return;
582 }
583 FitCaretMetricsToTouchPoint(caretMetrics, touchOffset);
584 OffsetF caretOffset = caretMetrics.offset;
585 RectF caretRect;
586 caretRect.SetOffset(caretOffset);
587 auto pattern = pattern_.Upgrade();
588 CHECK_NULL_VOID(pattern);
589 auto textField = DynamicCast<TextFieldPattern>(pattern);
590 CHECK_NULL_VOID(textField);
591 caretRect.SetSize({ caretInfo_.rect.Width(),
592 LessOrEqual(caretMetrics.height, 0.0) ? textField->PreferredLineHeight() : caretMetrics.height });
593
594 // Adjusts one character width.
595 float boundaryAdjustment = 0.0f;
596 MoveHandleToContentRect(caretRect, boundaryAdjustment);
597 caretInfo_.rect = caretRect;
598 UpdateCaretOriginalRect(caretMetrics.offset);
599 auto index = ConvertTouchOffsetToPosition(touchOffset);
600 AdjustCursorPosition(index, touchOffset);
601 UpdateCaretIndex(index);
602 }
603
UpdateFirstHandleOffset()604 void TextSelectController::UpdateFirstHandleOffset()
605 {
606 CaretMetricsF caretMetrics;
607 CalcCaretMetricsByPosition(
608 GetFirstHandleIndex(), caretMetrics, HasReverse() ? TextAffinity::UPSTREAM : TextAffinity::DOWNSTREAM);
609 firstHandleInfo_.rect.SetOffset(caretMetrics.offset);
610 firstHandleInfo_.rect.SetHeight(caretMetrics.height);
611 firstHandleInfo_.rect.SetWidth(SelectHandleInfo::GetDefaultLineWidth().ConvertToPx());
612 AdjustHandleOffset(firstHandleInfo_.rect);
613 AdjustHandleOffsetWithBoundary(firstHandleInfo_.rect);
614 }
615
UpdateSecondHandleOffset()616 void TextSelectController::UpdateSecondHandleOffset()
617 {
618 CaretMetricsF caretMetrics;
619 CalcCaretMetricsByPosition(
620 GetSecondHandleIndex(), caretMetrics, HasReverse() ? TextAffinity::DOWNSTREAM : TextAffinity::UPSTREAM);
621 secondHandleInfo_.rect.SetOffset(caretMetrics.offset);
622 secondHandleInfo_.rect.SetHeight(caretMetrics.height);
623 secondHandleInfo_.rect.SetWidth(SelectHandleInfo::GetDefaultLineWidth().ConvertToPx());
624 AdjustHandleOffset(secondHandleInfo_.rect);
625 AdjustHandleOffsetWithBoundary(secondHandleInfo_.rect);
626 }
627
UpdateCaretOffset(TextAffinity textAffinity,bool moveHandle)628 void TextSelectController::UpdateCaretOffset(TextAffinity textAffinity, bool moveHandle)
629 {
630 textAffinity_ = textAffinity;
631 if (contentController_->IsEmpty()) {
632 SetCaretRectAtEmptyValue();
633 return;
634 }
635 CaretMetricsF caretMetrics;
636 CalcCaretMetricsByPosition(GetCaretIndex(), caretMetrics, textAffinity);
637
638 RectF caretRect;
639 caretRect.SetOffset(caretMetrics.offset);
640 auto pattern = pattern_.Upgrade();
641 CHECK_NULL_VOID(pattern);
642 auto textField = DynamicCast<TextFieldPattern>(pattern);
643 CHECK_NULL_VOID(textField);
644 caretRect.SetSize(SizeF(caretInfo_.rect.Width(),
645 LessOrEqual(caretMetrics.height, 0.0) ? textField->PreferredLineHeight() : caretMetrics.height));
646 caretInfo_.rect = caretRect;
647 if (moveHandle) {
648 MoveHandleToContentRect(caretInfo_.rect, 0.0f);
649 } else {
650 AdjustHandleAtEdge(caretInfo_.rect);
651 }
652 UpdateCaretOriginalRect(caretMetrics.offset);
653 }
654
UpdateCaretOffset(const OffsetF & offset)655 void TextSelectController::UpdateCaretOffset(const OffsetF& offset)
656 {
657 caretInfo_.rect.SetOffset(caretInfo_.rect.GetOffset() + offset);
658 caretInfo_.originalRect.SetOffset(caretInfo_.originalRect.GetOffset() + offset);
659 secondHandleInfo_.UpdateOffset(caretInfo_.rect.GetOffset() + offset);
660 }
661
UpdateSecondHandleInfoByMouseOffset(const Offset & localOffset)662 void TextSelectController::UpdateSecondHandleInfoByMouseOffset(const Offset& localOffset)
663 {
664 auto index = ConvertTouchOffsetToPosition(localOffset);
665 if (GreatNotEqual(localOffset.GetX(), contentRect_.GetX() + contentRect_.Width()) && paragraph_) {
666 float boundaryAdjustment = paragraph_->GetCharacterWidth(caretInfo_.index);
667 index = ConvertTouchOffsetToPosition({localOffset.GetX() + boundaryAdjustment, localOffset.GetY()});
668 }
669 MoveSecondHandleToContentRect(index, false, false);
670 caretInfo_.index = index;
671 UpdateCaretRectByPositionNearTouchOffset(index, localOffset);
672 auto caretRect = GetCaretRect();
673 MoveHandleToContentRect(caretRect);
674 caretInfo_.rect = caretRect;
675 }
676
MoveSecondHandleByKeyBoard(int32_t index,std::optional<TextAffinity> textAffinity)677 void TextSelectController::MoveSecondHandleByKeyBoard(int32_t index, std::optional<TextAffinity> textAffinity)
678 {
679 index = std::clamp(index, 0, static_cast<int32_t>(contentController_->GetTextUtf16Value().length()));
680 MoveSecondHandleToContentRect(index);
681 caretInfo_.index = index;
682 auto caretTextAffinity = HasReverse() ? TextAffinity::DOWNSTREAM : TextAffinity::UPSTREAM;
683 if (textAffinity) {
684 caretTextAffinity = textAffinity.value();
685 }
686 UpdateCaretOffset(caretTextAffinity);
687 auto caretRect = GetCaretRect();
688 MoveHandleToContentRect(caretRect);
689 caretInfo_.rect = caretRect;
690 }
691
FireSelectEvent()692 void TextSelectController::FireSelectEvent()
693 {
694 if (!onAccessibilityCallback_) {
695 return;
696 }
697 bool needReport = !GetFirstIndex().has_value() || !GetSecondIndex().has_value();
698 bool secondIndexChange = false;
699 if (GetFirstIndex().has_value()) {
700 needReport |= GetFirstIndex().value() != firstHandleInfo_.index;
701 }
702
703 if (GetSecondIndex().has_value()) {
704 needReport |= GetSecondIndex().value() != secondHandleInfo_.index;
705 secondIndexChange = GetSecondIndex().value() != secondHandleInfo_.index;
706 }
707
708 auto pattern = pattern_.Upgrade();
709 CHECK_NULL_VOID(pattern);
710 auto textField = DynamicCast<TextFieldPattern>(pattern);
711 CHECK_NULL_VOID(textField);
712 auto eventHub = textField->GetOrCreateEventHub<TextFieldEventHub>();
713 CHECK_NULL_VOID(eventHub);
714
715 if (needReport && textField->IsModifyDone() && (textField->HasFocus()
716 || (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)))) {
717 UpdateFirstIndex(firstHandleInfo_.index);
718 UpdateSecondIndex(secondHandleInfo_.index);
719 onAccessibilityCallback_();
720 eventHub->FireOnSelectionChange(std::min(firstHandleInfo_.index, secondHandleInfo_.index),
721 std::max(firstHandleInfo_.index, secondHandleInfo_.index));
722 if (secondIndexChange) {
723 // when second index change, avoid caret in time
724 textField->TriggerAvoidWhenCaretGoesDown();
725 }
726 }
727 }
728
ResetHandles()729 void TextSelectController::ResetHandles()
730 {
731 firstHandleInfo_.index = caretInfo_.index;
732 secondHandleInfo_.index = caretInfo_.index;
733 UpdateFirstHandleOffset();
734 UpdateSecondHandleOffset();
735 }
736
NeedAIAnalysis(int32_t & index,const CaretUpdateType targetType,const Offset & touchOffset,std::chrono::duration<float,std::ratio<1,SECONDS_TO_MILLISECONDS>> timeout)737 bool TextSelectController::NeedAIAnalysis(int32_t& index, const CaretUpdateType targetType, const Offset& touchOffset,
738 std::chrono::duration<float, std::ratio<1, SECONDS_TO_MILLISECONDS>> timeout)
739 {
740 auto pattern = pattern_.Upgrade();
741 CHECK_NULL_RETURN(pattern, false);
742 auto textField = DynamicCast<TextFieldPattern>(pattern);
743 CHECK_NULL_RETURN(textField, false);
744
745 if (!InputAIChecker::NeedAIAnalysis(contentController_->GetTextUtf16Value().empty(), targetType, timeout)) {
746 return false;
747 }
748 if (IsClickAtBoundary(index, touchOffset) && targetType == CaretUpdateType::PRESSED) {
749 TAG_LOGI(AceLogTag::ACE_TEXTINPUT, "NeedAIAnalysis IsClickAtBoundary is boundary, return!");
750 return false;
751 }
752
753 if (textField->IsInPasswordMode()) {
754 TAG_LOGI(AceLogTag::ACE_TEXTINPUT, "NeedAIAnalysis IsInPasswordMode, return!");
755 return false;
756 }
757
758 if (contentController_->IsIndexBeforeOrInEmoji(index)) {
759 TAG_LOGI(AceLogTag::ACE_TEXTINPUT, "NeedAIAnalysis IsIndexBeforeOrInEmoji, return!");
760 return false;
761 }
762 return true;
763 }
764
AdjustCursorPosition(int32_t & index,const OHOS::Ace::Offset & touchOffset)765 void TextSelectController::AdjustCursorPosition(int32_t& index, const OHOS::Ace::Offset& touchOffset)
766 {
767 auto timeout = GetLastClickTime() - lastAiPosTimeStamp_;
768 if (NeedAIAnalysis(index, CaretUpdateType::PRESSED, touchOffset, timeout)) {
769 // consider to limit the whole string length
770 int32_t startIndex = -1;
771 int32_t subIndex = index;
772 // the subindex match the sub content,we do choose the subtext to ai analysis to avoid the content too large
773 std::string content = contentController_->GetSelectedLimitValue(subIndex, startIndex);
774 DataDetectorMgr::GetInstance().AdjustCursorPosition(subIndex, content, lastAiPosTimeStamp_, GetLastClickTime());
775 index = startIndex + subIndex;
776 TAG_LOGI(AceLogTag::ACE_RICH_TEXT, "ai pos, startIndex:%{public}d,subIndex:%{public}d", startIndex, subIndex);
777 }
778 }
779
AdjustWordSelection(int32_t & index,int32_t & start,int32_t & end,const OHOS::Ace::Offset & touchOffset)780 bool TextSelectController::AdjustWordSelection(
781 int32_t& index, int32_t& start, int32_t& end, const OHOS::Ace::Offset& touchOffset)
782 {
783 auto timeout = GetLastClickTime() - lastAiPosTimeStamp_;
784 if (NeedAIAnalysis(index, CaretUpdateType::DOUBLE_CLICK, touchOffset, timeout)) {
785 // consider the limint the whole string length
786 int32_t startIndex = -1;
787 int32_t subIndex = index;
788 // to avoid the content too large
789 std::string content = contentController_->GetSelectedLimitValue(subIndex, startIndex);
790 int32_t aiPosStart = -1;
791 int32_t aiPosEnd = -1;
792 DataDetectorMgr::GetInstance().AdjustWordSelection(subIndex, content, aiPosStart, aiPosEnd);
793 TAG_LOGI(AceLogTag::ACE_TEXTINPUT, "after ai ,startIndex:%{public}d-sub:%{public}d", aiPosStart, aiPosEnd);
794 if (aiPosStart >= 0 && aiPosEnd >= 0) {
795 index = startIndex + subIndex;
796 start = startIndex + aiPosStart;
797 end = startIndex + aiPosEnd;
798 return true;
799 }
800 }
801 return false;
802 }
803
IsClickAtBoundary(int32_t index,const OHOS::Ace::Offset & touchOffset)804 bool TextSelectController::IsClickAtBoundary(int32_t index, const OHOS::Ace::Offset& touchOffset)
805 {
806 if (InputAIChecker::IsSingleClickAtBoundary(index, contentController_->GetTextUtf16Value().length())) {
807 return true;
808 }
809
810 auto pattern = pattern_.Upgrade();
811 CHECK_NULL_RETURN(pattern, false);
812 auto textField = DynamicCast<TextFieldPattern>(pattern);
813 CHECK_NULL_RETURN(textField, false);
814 auto textRect = textField->GetTextRect();
815
816 CaretMetricsF caretMetrics;
817 CalcCaretMetricsByPositionNearTouchOffset(
818 index, caretMetrics, OffsetF(static_cast<float>(touchOffset.GetX()), static_cast<float>(touchOffset.GetY())));
819
820 return InputAIChecker::IsMultiClickAtBoundary(caretMetrics.offset, textRect);
821 }
822
GetLastClickTime() const823 const TimeStamp& TextSelectController::GetLastClickTime() const
824 {
825 auto pattern = pattern_.Upgrade();
826 CHECK_NULL_RETURN(pattern, lastAiPosTimeStamp_);
827 auto textField = DynamicCast<TextFieldPattern>(pattern);
828 CHECK_NULL_RETURN(textField, lastAiPosTimeStamp_);
829 return textField->GetLastClickTime();
830 }
831
IsTouchAtLineEnd(const Offset & localOffset) const832 bool TextSelectController::IsTouchAtLineEnd(const Offset& localOffset) const
833 {
834 CHECK_NULL_RETURN(paragraph_ && !contentController_->IsEmpty(), false);
835 auto index = ConvertTouchOffsetToPosition(localOffset);
836 if (index == static_cast<int32_t>(contentController_->GetTextUtf16Value().length())) {
837 return true;
838 }
839 auto pattern = pattern_.Upgrade();
840 CHECK_NULL_RETURN(pattern, false);
841 auto textField = DynamicCast<TextFieldPattern>(pattern);
842 CHECK_NULL_RETURN(textField, false);
843 auto textRect = textField->GetTextRect();
844 auto offset = localOffset - Offset(textRect.GetX(), textRect.GetY());
845 LineMetrics lineMetrics;
846 if (paragraph_->GetLineMetricsByCoordinate(offset, lineMetrics)) {
847 if (textField->IsLTRLayout()) {
848 return GreatNotEqual(offset.GetX(), lineMetrics.x + lineMetrics.width);
849 } else {
850 return LessNotEqual(offset.GetX(), lineMetrics.x);
851 }
852 }
853 return false;
854 }
855
GetTouchLinePos(const Offset & localOffset)856 TouchPosition TextSelectController::GetTouchLinePos(const Offset& localOffset)
857 {
858 CHECK_NULL_RETURN(paragraph_, TouchPosition::MID);
859 auto pattern = pattern_.Upgrade();
860 CHECK_NULL_RETURN(pattern, TouchPosition::MID);
861 auto textField = DynamicCast<TextFieldPattern>(pattern);
862 CHECK_NULL_RETURN(textField, TouchPosition::MID);
863 if (contentController_->IsEmpty()) {
864 return textField->IsLTRLayout() ? TouchPosition::RIGHT : TouchPosition::LEFT;
865 }
866 auto index = ConvertTouchOffsetToPosition(localOffset);
867 if (index == 0) {
868 return textField->IsLTRLayout() ? TouchPosition::LEFT : TouchPosition::RIGHT;
869 }
870 if (index == static_cast<int32_t>(contentController_->GetTextUtf16Value().length())) {
871 return textField->IsLTRLayout() ? TouchPosition::RIGHT : TouchPosition::LEFT;
872 }
873 auto textRect = textField->GetTextRect();
874 auto offset = localOffset - Offset(textRect.GetX(), textRect.GetY());
875 LineMetrics lineMetrics;
876 if (paragraph_->GetLineMetricsByCoordinate(offset, lineMetrics)) {
877 if (GreatNotEqual(offset.GetX(), lineMetrics.x + lineMetrics.width)) {
878 return TouchPosition::RIGHT;
879 }
880 if (LessNotEqual(offset.GetX(), lineMetrics.x)) {
881 return TouchPosition::LEFT;
882 }
883 }
884 return TouchPosition::MID;
885 }
886
UpdateSelectWithBlank(const Offset & localOffset)887 void TextSelectController::UpdateSelectWithBlank(const Offset& localOffset)
888 {
889 auto pattern = pattern_.Upgrade();
890 CHECK_NULL_VOID(pattern);
891 auto textField = DynamicCast<TextFieldPattern>(pattern);
892 CHECK_NULL_VOID(textField);
893 auto textRect = textField->GetTextRect();
894 auto contentRect = textField->GetTextContentRect();
895 auto touchLocalOffset = localOffset;
896 if (textField->IsTextArea() && GreatNotEqual(touchLocalOffset.GetY(), textRect.Bottom())) {
897 // click at end of a paragraph.
898 touchLocalOffset.SetX(textField->IsLTRLayout() ? contentRect.Right() : textRect.Left());
899 }
900 if (IsTouchAtLineEnd(touchLocalOffset)) {
901 UpdateCaretInfoByOffset(touchLocalOffset);
902 } else {
903 UpdateSelectByOffset(localOffset);
904 }
905 }
906
SetCaretRectAtEmptyValue()907 void TextSelectController::SetCaretRectAtEmptyValue()
908 {
909 caretInfo_.rect = CalculateEmptyValueCaretRect();
910 caretInfo_.originalRect = CalculateEmptyValueCaretRect(caretInfo_.originalRect.Width());
911 }
912
UpdateCaretOriginalRect(const OffsetF & offset)913 void TextSelectController::UpdateCaretOriginalRect(const OffsetF& offset)
914 {
915 caretInfo_.originalRect.SetOffset(OffsetF(offset.GetX(), caretInfo_.rect.Top()));
916 caretInfo_.originalRect.SetHeight(caretInfo_.rect.Height());
917 AdjustHandleAtEdge(caretInfo_.originalRect);
918 }
919
AdjustHandleInBoundary(RectF & handleRect) const920 void TextSelectController::AdjustHandleInBoundary(RectF& handleRect) const
921 {
922 auto pattern = pattern_.Upgrade();
923 CHECK_NULL_VOID(pattern);
924 auto textField = DynamicCast<TextFieldPattern>(pattern);
925 CHECK_NULL_VOID(textField);
926 // Adjusted handle to the content area when they are at the content area boundary.
927 if (LessNotEqual(handleRect.GetX(), contentRect_.GetX())) {
928 handleRect.SetLeft(contentRect_.GetX());
929 }
930
931 auto textRectRightBoundary = contentRect_.GetX() + contentRect_.Width();
932 if (GreatNotEqual(handleRect.GetX() + handleRect.Width(), textRectRightBoundary) &&
933 GreatNotEqual(contentRect_.Width(), 0.0) && !textField->GetTextUtf16Value().empty()) {
934 handleRect.SetLeft(textRectRightBoundary - handleRect.Width());
935 }
936 }
937
AdjustHandleOffsetWithBoundary(RectF & handleRect)938 void TextSelectController::AdjustHandleOffsetWithBoundary(RectF& handleRect)
939 {
940 auto pattern = pattern_.Upgrade();
941 CHECK_NULL_VOID(pattern);
942 auto textField = DynamicCast<TextFieldPattern>(pattern);
943 CHECK_NULL_VOID(textField);
944 if (textField->IsTextArea()) {
945 AdjustHandleInBoundary(handleRect);
946 return;
947 }
948 auto textRect = textField->GetTextRect();
949 auto contentRect = textField->GetContentRect();
950 // TextInput scroll to the far right.
951 if (NearEqual(textRect.Right(), contentRect.Right()) && GreatNotEqual(handleRect.Right(), contentRect.Right()) &&
952 GreatNotEqual(contentRect.Width(), 0.0) && !textField->GetTextUtf16Value().empty()) {
953 handleRect.SetLeft(contentRect.Right() - handleRect.Width());
954 }
955 // TextInput scroll to the far left.
956 if (NearEqual(textRect.Left(), contentRect.Left()) && LessNotEqual(handleRect.Left(), contentRect.Left())) {
957 handleRect.SetLeft(contentRect.Left());
958 }
959 }
960
AdjustAllHandlesWithBoundary()961 void TextSelectController::AdjustAllHandlesWithBoundary()
962 {
963 AdjustHandleOffsetWithBoundary(firstHandleInfo_.rect);
964 AdjustHandleOffsetWithBoundary(secondHandleInfo_.rect);
965 }
966 } // namespace OHOS::Ace::NG