1 /*
2 * Copyright (c) 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
16 #include "core/components_ng/pattern/text_drag/text_drag_pattern.h"
17
18 #include <algorithm>
19
20 #include "base/utils/utils.h"
21 #include "core/components/container_modal/container_modal_constants.h"
22 #include "core/components/text/text_theme.h"
23 #include "core/components_ng/pattern/text/text_pattern.h"
24 #include "core/components_ng/pattern/text_drag/text_drag_base.h"
25 #include "core/components_ng/render/drawing.h"
26 #include "core/components_v2/inspector/inspector_constants.h"
27
28 namespace {
29 // uncertainty range when comparing selectedTextBox to contentRect
30 constexpr float BOX_EPSILON = 0.2f;
31 constexpr float CONSTANT_HALF = 2.0f;
32 } // namespace
33
34 namespace OHOS::Ace::NG {
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & dirty,const DirtySwapConfig & config)35 bool TextDragPattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& dirty, const DirtySwapConfig& config)
36 {
37 return true;
38 }
39
GetFirstBoxRect(const std::vector<RectF> & boxes,const RectF & contentRect,const float textStartY)40 const RectF GetFirstBoxRect(const std::vector<RectF>& boxes, const RectF& contentRect, const float textStartY)
41 {
42 for (const auto& box : boxes) {
43 if (box.Bottom() + textStartY > contentRect.Top() + BOX_EPSILON) {
44 return box;
45 }
46 }
47 return boxes.front();
48 } // Obtains the first line in the visible area of the text box, including the truncated part.
49
GetLastBoxRect(const std::vector<RectF> & boxes,const RectF & contentRect,const float textStartY)50 const RectF GetLastBoxRect(const std::vector<RectF>& boxes, const RectF& contentRect, const float textStartY)
51 {
52 bool hasResult = false;
53 RectF result;
54 RectF preBox;
55 auto maxBottom = contentRect.GetY() + SystemProperties::GetDevicePhysicalHeight();
56 for (const auto& box : boxes) {
57 auto caculateBottom = box.Bottom() + textStartY;
58 bool isReachingBottom = (caculateBottom >= maxBottom) || (caculateBottom >= contentRect.Bottom());
59 if (isReachingBottom && !hasResult) {
60 result = box;
61 hasResult = true;
62 auto isBoxExceedContent = GreatOrEqual(result.Top() + textStartY, contentRect.Bottom()) &&
63 LessNotEqual(preBox.Bottom() + textStartY, contentRect.Bottom());
64 isBoxExceedContent = isBoxExceedContent || (GreatOrEqual(result.Top() + textStartY, maxBottom) &&
65 LessNotEqual(preBox.Bottom() + textStartY, maxBottom));
66 CHECK_NULL_RETURN(!isBoxExceedContent, preBox);
67 continue;
68 }
69 if (!hasResult) {
70 preBox = box;
71 }
72 if (hasResult && box.Bottom() == result.Bottom()) {
73 result = box;
74 } else if (hasResult && box.Bottom() != result.Bottom()) {
75 return result;
76 }
77 }
78 return boxes.back();
79 } // Obtains the last line in the visible area of the text box, including the truncated part.
80
CreateDragNode(const RefPtr<FrameNode> & hostNode)81 RefPtr<FrameNode> TextDragPattern::CreateDragNode(const RefPtr<FrameNode>& hostNode)
82 {
83 CHECK_NULL_RETURN(hostNode, nullptr);
84 auto hostPattern = hostNode->GetPattern<TextDragBase>();
85 const auto nodeId = ElementRegister::GetInstance()->MakeUniqueId();
86 auto dragNode = FrameNode::GetOrCreateFrameNode(
87 V2::TEXTDRAG_ETS_TAG, nodeId, []() { return AceType::MakeRefPtr<TextDragPattern>(); });
88 auto dragContext = dragNode->GetRenderContext();
89 auto hostContext = hostNode->GetRenderContext();
90 if (hostContext->HasForegroundColor()) {
91 dragContext->UpdateForegroundColor(hostContext->GetForegroundColor().value());
92 }
93 if (hostContext->HasForegroundColorStrategy()) {
94 dragContext->UpdateForegroundColorStrategy(hostContext->GetForegroundColorStrategy().value());
95 }
96 auto dragPattern = dragNode->GetPattern<TextDragPattern>();
97 auto data = CalculateTextDragData(hostPattern, dragNode);
98 TAG_LOGI(AceLogTag::ACE_TEXT, "CreateDragNode SelectPositionInfo startX = %{public}f, startY = %{public}f,\
99 endX = %{public}f, endY = %{public}f, globalX = %{public}f, globalY = %{public}f",
100 data.selectPosition_.startX_, data.selectPosition_.startY_,
101 data.selectPosition_.endX_, data.selectPosition_.endY_,
102 data.selectPosition_.globalX_, data.selectPosition_.globalY_);
103 dragPattern->Initialize(hostPattern->GetDragParagraph(), data);
104 dragPattern->SetLastLineHeight(data.lineHeight_);
105
106 CalcSize size(NG::CalcLength(dragPattern->GetFrameWidth()), NG::CalcLength(dragPattern->GetFrameHeight()));
107 dragNode->GetLayoutProperty()->UpdateUserDefinedIdealSize(size);
108 return dragNode;
109 }
110
CalculateFloatTitleOffset(RefPtr<FrameNode> & dragNode,OffsetF & offset)111 void TextDragPattern::CalculateFloatTitleOffset(RefPtr<FrameNode>& dragNode, OffsetF& offset)
112 {
113 auto pipeline = dragNode->GetContext();
114 CHECK_NULL_VOID(pipeline);
115 auto stageManager = pipeline->GetStageManager();
116 CHECK_NULL_VOID(stageManager);
117 auto stageNode = stageManager->GetStageNode();
118 CHECK_NULL_VOID(stageNode);
119 auto stageOffset = stageNode->GetTransformRelativeOffset();
120 offset -= stageOffset;
121 }
122
CalculateTextDragData(RefPtr<TextDragBase> & pattern,RefPtr<FrameNode> & dragNode)123 TextDragData TextDragPattern::CalculateTextDragData(RefPtr<TextDragBase>& pattern, RefPtr<FrameNode>& dragNode)
124 {
125 auto dragContext = dragNode->GetRenderContext();
126 auto dragPattern = dragNode->GetPattern<TextDragPattern>();
127 OffsetF textStartOffset = {pattern->GetTextRect().GetX(),
128 pattern->IsTextArea() ? pattern->GetTextRect().GetY() : pattern->GetTextContentRect().GetY()};
129 auto contentRect = pattern->GetTextContentRect(true);
130 float bothOffset = TEXT_DRAG_OFFSET.ConvertToPx() * CONSTANT_HALF;
131 auto boxes = pattern->GetTextBoxes();
132 CHECK_NULL_RETURN(!boxes.empty(), {});
133 while (!boxes.empty() && NearZero(boxes.back().Width())) {
134 boxes.pop_back();
135 }
136 auto globalOffset = pattern->GetParentGlobalOffset();
137 CalculateFloatTitleOffset(dragNode, globalOffset);
138 RectF leftHandler = GetHandler(true, boxes, contentRect, globalOffset, textStartOffset);
139 RectF rightHandler = GetHandler(false, boxes, contentRect, globalOffset, textStartOffset);
140 AdjustHandlers(contentRect, leftHandler, rightHandler);
141 float width = rightHandler.GetX() - leftHandler.GetX();
142 float height = rightHandler.GetY() - leftHandler.GetY() + rightHandler.Height();
143 auto dragOffset = TEXT_DRAG_OFFSET.ConvertToPx();
144 float globalX = leftHandler.GetX() + globalOffset.GetX() - dragOffset;
145 float globalY = leftHandler.GetY() + globalOffset.GetY() - dragOffset;
146 auto box = boxes.front();
147 float delta = 0.0f;
148 float handlersDistance = width;
149 if (leftHandler.GetY() == rightHandler.GetY()) {
150 if (handlersDistance + bothOffset < TEXT_DRAG_MIN_WIDTH.ConvertToPx()) {
151 delta = TEXT_DRAG_MIN_WIDTH.ConvertToPx() - (handlersDistance + bothOffset);
152 width += delta;
153 globalX -= delta / CONSTANT_HALF;
154 }
155 } else {
156 globalX = contentRect.Left() + globalOffset.GetX() - dragOffset;
157 dragPattern->AdjustMaxWidth(width, contentRect, boxes);
158 }
159 float contentX = (leftHandler.GetY() == rightHandler.GetY() ? box.Left() : 0) - dragOffset - delta / CONSTANT_HALF;
160 dragPattern->SetContentOffset({contentX, box.Top() - dragOffset});
161 dragContext->UpdatePosition(OffsetT<Dimension>(Dimension(globalX), Dimension(globalY)));
162 auto offsetXValue = globalOffset.GetX() - globalX;
163 auto offsetYValue = globalOffset.GetY() - globalY;
164 RectF rect(textStartOffset.GetX() + offsetXValue, textStartOffset.GetY() + offsetYValue, width, height);
165 SelectPositionInfo info(leftHandler.GetX() + offsetXValue, leftHandler.GetY() + offsetYValue,
166 rightHandler.GetX() + offsetXValue, rightHandler.GetY() + offsetYValue);
167 info.InitGlobalInfo(globalX, globalY);
168 TextDragData data(rect, width + bothOffset, height + bothOffset, leftHandler.Height(), rightHandler.Height());
169 data.initSelecitonInfo(info, leftHandler.GetY() == rightHandler.GetY());
170 return data;
171 }
172
AdjustMaxWidth(float & width,const RectF & contentRect,const std::vector<RectF> & boxes)173 void TextDragPattern::AdjustMaxWidth(float& width, const RectF& contentRect, const std::vector<RectF>& boxes)
174 {
175 width = contentRect.Width();
176 CHECK_NULL_VOID(!boxes.empty());
177 float startX = boxes.front().Left();
178 float endX = boxes.front().Right();
179 for (const auto& box : boxes) {
180 startX = std::min(startX, box.Left());
181 endX = std::max(endX, box.Right());
182 }
183 startX = std::min(0.0f, startX);
184 width = std::abs(startX - endX);
185 }
186
GetHandler(const bool isLeftHandler,const std::vector<RectF> boxes,const RectF contentRect,const OffsetF globalOffset,const OffsetF textStartOffset)187 RectF TextDragPattern::GetHandler(const bool isLeftHandler, const std::vector<RectF> boxes, const RectF contentRect,
188 const OffsetF globalOffset, const OffsetF textStartOffset)
189 {
190 auto adjustedTextStartY = textStartOffset.GetY() + std::min(globalOffset.GetY(), 0.0f);
191 auto box = isLeftHandler ? GetFirstBoxRect(boxes, contentRect, adjustedTextStartY) :
192 GetLastBoxRect(boxes, contentRect, adjustedTextStartY);
193 auto handlerX = (isLeftHandler ? box.Left() : box.Right()) + textStartOffset.GetX();
194 return {handlerX, box.Top() + textStartOffset.GetY(), 0, box.Height()};
195 }
196
AdjustHandlers(const RectF contentRect,RectF & leftHandler,RectF & rightHandler)197 void TextDragPattern::AdjustHandlers(const RectF contentRect, RectF& leftHandler, RectF& rightHandler)
198 {
199 if (leftHandler.GetY() != rightHandler.GetY()) {
200 return;
201 }
202 leftHandler.SetLeft(std::max(leftHandler.GetX(), contentRect.Left()));
203 rightHandler.SetLeft(std::min(rightHandler.GetX(), contentRect.Right()));
204 }
205
GenerateClipPath()206 std::shared_ptr<RSPath> TextDragPattern::GenerateClipPath()
207 {
208 std::shared_ptr<RSPath> path = std::make_shared<RSPath>();
209 auto selectPosition = GetSelectPosition();
210 float startX = selectPosition.startX_;
211 float startY = selectPosition.startY_;
212 float textStart = GetTextRect().GetX();
213 float textEnd = textStart + GetTextRect().Width();
214 float endX = selectPosition.endX_;
215 float endY = selectPosition.endY_;
216 auto lineHeight = GetLineHeight();
217 if (OneLineSelected()) {
218 path->MoveTo(startX, endY);
219 path->LineTo(endX, endY);
220 path->LineTo(endX, endY + lineHeight);
221 path->LineTo(startX, endY + lineHeight);
222 path->LineTo(startX, endY);
223 } else {
224 endX = std::min(selectPosition.endX_, textEnd);
225 path->MoveTo(startX, startY);
226 path->LineTo(textEnd, startY);
227 path->LineTo(textEnd, endY);
228 path->LineTo(endX, endY);
229 path->LineTo(endX, endY + lastLineHeight_);
230 path->LineTo(textStart, endY + lastLineHeight_);
231 path->LineTo(textStart, startY + lineHeight);
232 path->LineTo(startX, startY + lineHeight);
233 path->LineTo(startX, startY);
234 }
235 return path;
236 }
237
GenerateBackgroundPath(float offset,float radiusRatio)238 std::shared_ptr<RSPath> TextDragPattern::GenerateBackgroundPath(float offset, float radiusRatio)
239 {
240 std::shared_ptr<RSPath> path = std::make_shared<RSPath>();
241 std::vector<TextPoint> points;
242 GenerateBackgroundPoints(points, offset);
243 CalculateLineAndArc(points, path, radiusRatio);
244 return path;
245 }
246
GenerateSelBackgroundPath(float offset)247 std::shared_ptr<RSPath> TextDragPattern::GenerateSelBackgroundPath(float offset)
248 {
249 std::shared_ptr<RSPath> path = std::make_shared<RSPath>();
250 std::vector<TextPoint> points;
251 GenerateBackgroundPoints(points, offset);
252 CalculateLine(points, path);
253 return path;
254 }
255
GenerateBackgroundPoints(std::vector<TextPoint> & points,float offset,bool needAdjust)256 void TextDragPattern::GenerateBackgroundPoints(std::vector<TextPoint>& points, float offset, bool needAdjust)
257 {
258 auto radius = GetDragCornerRadius().ConvertToPx();
259 auto bothOffset = offset * 2; // 2 : double
260 auto minWidth = TEXT_DRAG_MIN_WIDTH.ConvertToPx();
261 auto selectPosition = GetSelectPosition();
262 float startX = selectPosition.startX_;
263 float startY = selectPosition.startY_;
264 float endX = selectPosition.endX_;
265 float endY = selectPosition.endY_;
266 float textStart = GetTextRect().GetX();
267 float textEnd = textStart + GetTextRect().Width();
268 auto lineHeight = GetLineHeight();
269 if (OneLineSelected()) {
270 if (needAdjust && (endX - startX) + bothOffset < minWidth) {
271 float delta = minWidth - ((endX - startX) + bothOffset);
272 startX -= delta / 2.0f; // 2 : half
273 endX += delta / 2.0f; // 2 : half
274 }
275 points.push_back(TextPoint(startX - offset, startY - offset));
276 points.push_back(TextPoint(endX + offset, endY - offset));
277 points.push_back(TextPoint(endX + offset, endY + lineHeight + offset));
278 points.push_back(TextPoint(startX - offset, endY + lineHeight + offset));
279 points.push_back(TextPoint(startX - offset, endY - offset));
280 points.push_back(TextPoint(endX + offset, endY - offset));
281 } else {
282 points.push_back(TextPoint(startX - offset, startY - offset));
283 points.push_back(TextPoint(textEnd + offset, startY - offset));
284 if (textEnd - radius < endX + radius) {
285 points.push_back(TextPoint(textEnd + offset, endY + lastLineHeight_ + offset));
286 } else {
287 points.push_back(TextPoint(textEnd + offset, endY + offset));
288 points.push_back(TextPoint(endX + offset, endY + offset));
289 points.push_back(TextPoint(endX + offset, endY + lastLineHeight_ + offset));
290 }
291 points.push_back(TextPoint(textStart - offset, endY + lastLineHeight_ + offset));
292 if (startX - radius < textStart + radius) {
293 points[0] = TextPoint(textStart - offset, startY - offset);
294 points.push_back(TextPoint(textStart - offset, startY - offset));
295 } else {
296 points.push_back(TextPoint(textStart - offset, startY + lineHeight - offset));
297 points.push_back(TextPoint(startX - offset, startY + lineHeight - offset));
298 points.push_back(TextPoint(startX - offset, startY - offset));
299 }
300 points.push_back(TextPoint(textEnd + offset, startY - offset));
301 }
302 }
303
CalculateLineAndArc(std::vector<TextPoint> & points,std::shared_ptr<RSPath> & path,float radiusRatio)304 void TextDragPattern::CalculateLineAndArc(std::vector<TextPoint>& points, std::shared_ptr<RSPath>& path,
305 float radiusRatio)
306 {
307 auto originRadius = GetDragCornerRadius().ConvertToPx();
308 auto radius = originRadius * radiusRatio;
309 path->MoveTo(points[0].x + radius, points[0].y);
310 auto frontPoint = points[0];
311 size_t step = 2;
312 for (size_t i = 0; i + step < points.size(); i++) {
313 auto firstPoint = points[i];
314 auto crossPoint = points[i + 1];
315 auto secondPoint = points[i + step];
316 float tempRadius = radius;
317 if (crossPoint.y == firstPoint.y) {
318 int directionX = (crossPoint.x - firstPoint.x) > 0 ? 1 : -1;
319 int directionY = (secondPoint.y - crossPoint.y) > 0 ? 1 : -1;
320 auto direction =
321 (directionX * directionY > 0) ? RSPathDirection::CW_DIRECTION : RSPathDirection::CCW_DIRECTION;
322 bool isInwardFoldingCorner = frontPoint.x == firstPoint.x && firstPoint.y == crossPoint.y &&
323 secondPoint.x == crossPoint.x;
324 isInwardFoldingCorner = isInwardFoldingCorner && (frontPoint.y - firstPoint.y) *
325 (crossPoint.y - secondPoint.y) > 0;
326 if (isInwardFoldingCorner) {
327 radius = std::min(std::abs(secondPoint.y - crossPoint.y) / CONSTANT_HALF, tempRadius);
328 }
329 path->LineTo(crossPoint.x - radius * directionX, crossPoint.y);
330 path->ArcTo(radius, radius, 0.0f, direction, crossPoint.x, crossPoint.y + radius * directionY);
331 } else {
332 int directionX = (secondPoint.x - crossPoint.x) > 0 ? 1 : -1;
333 int directionY = (crossPoint.y - firstPoint.y) > 0 ? 1 : -1;
334 auto direction =
335 (directionX * directionY < 0) ? RSPathDirection::CW_DIRECTION : RSPathDirection::CCW_DIRECTION;
336 bool isInwardFoldingCorner = frontPoint.y == firstPoint.y && firstPoint.x == crossPoint.x &&
337 secondPoint.y == crossPoint.y;
338 isInwardFoldingCorner = isInwardFoldingCorner && (frontPoint.x - firstPoint.x) *
339 (crossPoint.x - secondPoint.x) > 0;
340 if (isInwardFoldingCorner) {
341 radius = std::min(std::abs(firstPoint.y - crossPoint.y) / CONSTANT_HALF, tempRadius);
342 }
343 path->LineTo(crossPoint.x, crossPoint.y - radius * directionY);
344 path->ArcTo(radius, radius, 0.0f, direction, crossPoint.x + radius * directionX, secondPoint.y);
345 }
346 radius = tempRadius;
347 frontPoint = firstPoint;
348 }
349 path->MoveTo(points[0].x + radius, points[0].y);
350 }
351
CalculateLine(std::vector<TextPoint> & points,std::shared_ptr<RSPath> & path)352 void TextDragPattern::CalculateLine(std::vector<TextPoint>& points, std::shared_ptr<RSPath>& path)
353 {
354 auto radius = GetDragCornerRadius().ConvertToPx();
355 path->MoveTo(points[0].x + radius, points[0].y);
356 size_t step = 2;
357 for (size_t i = 0; i + step < points.size(); i++) {
358 auto crossPoint = points[i + 1];
359 path->LineTo(crossPoint.x, crossPoint.y);
360 }
361 }
362
GetDragBackgroundColor()363 Color TextDragPattern::GetDragBackgroundColor()
364 {
365 auto pipeline = PipelineContext::GetCurrentContextSafelyWithCheck();
366 CHECK_NULL_RETURN(pipeline, Color(TEXT_DRAG_COLOR_BG));
367 auto textTheme = pipeline->GetTheme<TextTheme>();
368 CHECK_NULL_RETURN(textTheme, Color(TEXT_DRAG_COLOR_BG));
369 return textTheme->GetDragBackgroundColor();
370 }
371
GetDragCornerRadius()372 Dimension TextDragPattern::GetDragCornerRadius()
373 {
374 auto deviceType = SystemProperties::GetDeviceType();
375 if (deviceType == DeviceType::TWO_IN_ONE) {
376 return TEXT_DRAG_RADIUS_2IN1;
377 }
378 return TEXT_DRAG_RADIUS;
379 }
380 } // namespace OHOS::Ace::NG
381