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