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