• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 deviceHeight = SystemProperties::GetDevicePhysicalHeight();
56     auto container = Container::CurrentSafely();
57     if (container && container->GetDisplayInfo()) {
58         deviceHeight = container->GetDisplayInfo()->GetHeight();
59     }
60     auto maxBottom = contentRect.GetY() + deviceHeight;
61     for (const auto& box : boxes) {
62         auto caculateBottom = box.Bottom() + textStartY;
63         bool isReachingBottom = (caculateBottom >= maxBottom) || (caculateBottom >= contentRect.Bottom());
64         if (isReachingBottom && !hasResult) {
65             result = box;
66             hasResult = true;
67             auto isBoxExceedContent = GreatOrEqual(result.Top() + textStartY, contentRect.Bottom()) &&
68                 LessNotEqual(preBox.Bottom() + textStartY, contentRect.Bottom());
69             isBoxExceedContent = isBoxExceedContent || (GreatOrEqual(result.Top() + textStartY, maxBottom) &&
70                 LessNotEqual(preBox.Bottom() + textStartY, maxBottom));
71             CHECK_NULL_RETURN(!isBoxExceedContent, preBox);
72             continue;
73         }
74         if (!hasResult) {
75             preBox = box;
76         }
77         if (hasResult && box.Bottom() == result.Bottom()) {
78             result = box;
79         } else if (hasResult && box.Bottom() != result.Bottom()) {
80             return result;
81         }
82     }
83     return boxes.back();
84 } // Obtains the last line in the visible area of the text box, including the truncated part.
85 
CreateDragNode(const RefPtr<FrameNode> & hostNode)86 RefPtr<FrameNode> TextDragPattern::CreateDragNode(const RefPtr<FrameNode>& hostNode)
87 {
88     CHECK_NULL_RETURN(hostNode, nullptr);
89     auto hostPattern = hostNode->GetPattern<TextDragBase>();
90     const auto nodeId = ElementRegister::GetInstance()->MakeUniqueId();
91     auto dragNode = FrameNode::GetOrCreateFrameNode(
92         V2::TEXTDRAG_ETS_TAG, nodeId, []() { return AceType::MakeRefPtr<TextDragPattern>(); });
93     auto dragContext = dragNode->GetRenderContext();
94     auto hostContext = hostNode->GetRenderContext();
95     if (hostContext->HasForegroundColor()) {
96         dragContext->UpdateForegroundColor(hostContext->GetForegroundColor().value());
97     }
98     if (hostContext->HasForegroundColorStrategy()) {
99         dragContext->UpdateForegroundColorStrategy(hostContext->GetForegroundColorStrategy().value());
100     }
101     auto dragPattern = dragNode->GetPattern<TextDragPattern>();
102     auto data = CalculateTextDragData(hostPattern, dragNode);
103     TAG_LOGI(AceLogTag::ACE_TEXT, "CreateDragNode SelectPositionInfo startX = %{public}f, startY = %{public}f,\
104              endX = %{public}f, endY = %{public}f, globalX = %{public}f, globalY = %{public}f",
105              data.selectPosition_.startX_, data.selectPosition_.startY_,
106              data.selectPosition_.endX_, data.selectPosition_.endY_,
107              data.selectPosition_.globalX_, data.selectPosition_.globalY_);
108     dragPattern->Initialize(hostPattern->GetDragParagraph(), data);
109     dragPattern->SetLastLineHeight(data.lineHeight_);
110 
111     CalcSize size(NG::CalcLength(dragPattern->GetFrameWidth()), NG::CalcLength(dragPattern->GetFrameHeight()));
112     dragNode->GetLayoutProperty()->UpdateUserDefinedIdealSize(size);
113 
114     auto onDetachFromMainTreeCallback = [weak = WeakPtr<TextDragBase>(hostPattern)]() {
115         auto textDragBasePattern = weak.Upgrade();
116         CHECK_NULL_VOID(textDragBasePattern);
117         textDragBasePattern->OnDragNodeDetachFromMainTree();
118     };
119     dragPattern->SetOnDetachFromMainTree(std::move(onDetachFromMainTreeCallback));
120     return dragNode;
121 }
122 
CalculateOverlayOffset(RefPtr<FrameNode> & dragNode,OffsetF & offset)123 void TextDragPattern::CalculateOverlayOffset(RefPtr<FrameNode>& dragNode, OffsetF& offset)
124 {
125     auto pipeline = dragNode->GetContext();
126     CHECK_NULL_VOID(pipeline);
127     auto overlayManager = pipeline->GetOverlayManager();
128     CHECK_NULL_VOID(overlayManager);
129     auto rootNode = overlayManager->GetRootNode().Upgrade();
130     CHECK_NULL_VOID(rootNode);
131     auto rootGeometryNode = AceType::DynamicCast<FrameNode>(rootNode)->GetGeometryNode();
132     CHECK_NULL_VOID(rootGeometryNode);
133     auto rootOffset = rootGeometryNode->GetFrameOffset();
134     offset -= rootOffset;
135 }
136 
DropBlankLines(std::vector<RectF> & boxes)137 void TextDragPattern::DropBlankLines(std::vector<RectF>& boxes)
138 {
139     while (!boxes.empty() && NearZero(boxes.back().Width())) {
140         boxes.pop_back();
141     }
142     while (!boxes.empty() && NearZero(boxes.front().Width())) {
143         boxes.erase(boxes.begin());
144     }
145 }
146 
CalculateTextDragData(RefPtr<TextDragBase> & pattern,RefPtr<FrameNode> & dragNode)147 TextDragData TextDragPattern::CalculateTextDragData(RefPtr<TextDragBase>& pattern, RefPtr<FrameNode>& dragNode)
148 {
149     auto dragContext = dragNode->GetRenderContext();
150     auto dragPattern = dragNode->GetPattern<TextDragPattern>();
151     OffsetF textStartOffset = {pattern->GetTextRect().GetX(),
152         pattern->IsTextArea() ? pattern->GetTextRect().GetY() : pattern->GetTextContentRect().GetY()};
153     auto contentRect = pattern->GetTextContentRect(true);
154     float bothOffset = TEXT_DRAG_OFFSET.ConvertToPx() * CONSTANT_HALF;
155     auto boxes = pattern->GetTextBoxes();
156     CHECK_NULL_RETURN(!boxes.empty(), {});
157     DropBlankLines(boxes);
158     auto globalOffset = pattern->GetParentGlobalOffset();
159     CalculateOverlayOffset(dragNode, globalOffset);
160     RectF leftHandler = GetHandler(true, boxes, contentRect, globalOffset, textStartOffset);
161     RectF rightHandler = GetHandler(false, boxes, contentRect, globalOffset, textStartOffset);
162     AdjustHandlers(contentRect, leftHandler, rightHandler);
163     float width = rightHandler.GetX() - leftHandler.GetX();
164     float height = rightHandler.GetY() - leftHandler.GetY() + rightHandler.Height();
165     auto dragOffset = TEXT_DRAG_OFFSET.ConvertToPx();
166     float globalX = leftHandler.GetX() + globalOffset.GetX() - dragOffset;
167     float globalY = leftHandler.GetY() + globalOffset.GetY() - dragOffset;
168     auto box = boxes.front();
169     float delta = 0.0f;
170     float handlersDistance = width;
171     if (leftHandler.GetY() == rightHandler.GetY()) {
172         if (handlersDistance + bothOffset < TEXT_DRAG_MIN_WIDTH.ConvertToPx()) {
173             delta = TEXT_DRAG_MIN_WIDTH.ConvertToPx() - (handlersDistance + bothOffset);
174             width += delta;
175             globalX -= delta / CONSTANT_HALF;
176         }
177     } else {
178         globalX = contentRect.Left() + globalOffset.GetX() - dragOffset;
179         dragPattern->AdjustMaxWidth(width, contentRect, boxes);
180     }
181     float contentX = (leftHandler.GetY() == rightHandler.GetY() ? box.Left() : 0) - dragOffset - delta / CONSTANT_HALF;
182     dragPattern->SetContentOffset({contentX, box.Top() - dragOffset});
183     dragContext->UpdatePosition(OffsetT<Dimension>(Dimension(globalX), Dimension(globalY)));
184     auto offsetXValue = globalOffset.GetX() - globalX;
185     auto offsetYValue = globalOffset.GetY() - globalY;
186     RectF rect(textStartOffset.GetX() + offsetXValue, textStartOffset.GetY() + offsetYValue, width, height);
187     SelectPositionInfo info(leftHandler.GetX() + offsetXValue, leftHandler.GetY() + offsetYValue,
188         rightHandler.GetX() + offsetXValue, rightHandler.GetY() + offsetYValue);
189     info.InitGlobalInfo(globalX, globalY);
190     TextDragData data(rect, width + bothOffset, height + bothOffset, leftHandler.Height(), rightHandler.Height());
191     data.initSelecitonInfo(info, leftHandler.GetY() == rightHandler.GetY());
192     return data;
193 }
194 
AdjustMaxWidth(float & width,const RectF & contentRect,const std::vector<RectF> & boxes)195 void TextDragPattern::AdjustMaxWidth(float& width, const RectF& contentRect, const std::vector<RectF>& boxes)
196 {
197     width = contentRect.Width();
198     CHECK_NULL_VOID(!boxes.empty());
199     float startX = boxes.front().Left();
200     float endX = boxes.front().Right();
201     for (const auto& box : boxes) {
202         startX = std::min(startX, box.Left());
203         endX = std::max(endX, box.Right());
204     }
205     startX = std::min(0.0f, startX);
206     width = std::abs(startX - endX);
207 }
208 
GetHandler(const bool isLeftHandler,const std::vector<RectF> boxes,const RectF contentRect,const OffsetF globalOffset,const OffsetF textStartOffset)209 RectF TextDragPattern::GetHandler(const bool isLeftHandler, const std::vector<RectF> boxes, const RectF contentRect,
210     const OffsetF globalOffset, const OffsetF textStartOffset)
211 {
212     auto adjustedTextStartY = textStartOffset.GetY() + std::min(globalOffset.GetY(), 0.0f);
213     auto box = isLeftHandler ? GetFirstBoxRect(boxes, contentRect, adjustedTextStartY) :
214         GetLastBoxRect(boxes, contentRect, adjustedTextStartY);
215     auto handlerX = (isLeftHandler ? box.Left() : box.Right()) + textStartOffset.GetX();
216     return {handlerX, box.Top() + textStartOffset.GetY(), 0, box.Height()};
217 }
218 
AdjustHandlers(const RectF contentRect,RectF & leftHandler,RectF & rightHandler)219 void TextDragPattern::AdjustHandlers(const RectF contentRect, RectF& leftHandler, RectF& rightHandler)
220 {
221     if (leftHandler.GetY() != rightHandler.GetY()) {
222         return;
223     }
224     leftHandler.SetLeft(std::max(leftHandler.GetX(), contentRect.Left()));
225     rightHandler.SetLeft(std::min(rightHandler.GetX(), contentRect.Right()));
226 }
227 
GenerateClipPath()228 std::shared_ptr<RSPath> TextDragPattern::GenerateClipPath()
229 {
230     std::shared_ptr<RSPath> path = std::make_shared<RSPath>();
231     auto selectPosition = GetSelectPosition();
232     float startX = selectPosition.startX_;
233     float startY = selectPosition.startY_;
234     float textStart = GetTextRect().GetX();
235     float textEnd = textStart + GetTextRect().Width();
236     float endX = selectPosition.endX_;
237     float endY = selectPosition.endY_;
238     auto lineHeight = GetLineHeight();
239     if (OneLineSelected()) {
240         path->MoveTo(startX, endY);
241         path->LineTo(endX, endY);
242         path->LineTo(endX, endY + lineHeight);
243         path->LineTo(startX, endY + lineHeight);
244         path->LineTo(startX, endY);
245     } else {
246         endX = std::min(selectPosition.endX_, textEnd);
247         path->MoveTo(startX, startY);
248         path->LineTo(textEnd, startY);
249         path->LineTo(textEnd, endY);
250         path->LineTo(endX, endY);
251         path->LineTo(endX, endY + lastLineHeight_);
252         path->LineTo(textStart, endY + lastLineHeight_);
253         path->LineTo(textStart, startY + lineHeight);
254         path->LineTo(startX, startY + lineHeight);
255         path->LineTo(startX, startY);
256     }
257     return path;
258 }
259 
GenerateBackgroundPath(float offset,float radiusRatio)260 std::shared_ptr<RSPath> TextDragPattern::GenerateBackgroundPath(float offset, float radiusRatio)
261 {
262     std::shared_ptr<RSPath> path = std::make_shared<RSPath>();
263     std::vector<TextPoint> points;
264     GenerateBackgroundPoints(points, offset);
265     CalculateLineAndArc(points, path, radiusRatio);
266     return path;
267 }
268 
GenerateSelBackgroundPath(float offset)269 std::shared_ptr<RSPath> TextDragPattern::GenerateSelBackgroundPath(float offset)
270 {
271     std::shared_ptr<RSPath> path = std::make_shared<RSPath>();
272     std::vector<TextPoint> points;
273     GenerateBackgroundPoints(points, offset);
274     CalculateLine(points, path);
275     return path;
276 }
277 
GenerateBackgroundPoints(std::vector<TextPoint> & points,float offset,bool needAdjust)278 void TextDragPattern::GenerateBackgroundPoints(std::vector<TextPoint>& points, float offset, bool needAdjust)
279 {
280     auto radius = GetDragCornerRadius().ConvertToPx();
281     auto bothOffset = offset * 2; // 2 : double
282     auto minWidth = TEXT_DRAG_MIN_WIDTH.ConvertToPx();
283     auto selectPosition = GetSelectPosition();
284     float startX = selectPosition.startX_;
285     float startY = selectPosition.startY_;
286     float endX = selectPosition.endX_;
287     float endY = selectPosition.endY_;
288     float textStart = GetTextRect().GetX();
289     float textEnd = textStart + GetTextRect().Width();
290     auto lineHeight = GetLineHeight();
291     if (OneLineSelected()) {
292         if (needAdjust && (endX - startX) + bothOffset < minWidth) {
293             float delta = minWidth - ((endX - startX) + bothOffset);
294             startX -= delta / 2.0f; // 2 : half
295             endX += delta / 2.0f;   // 2 : half
296         }
297         points.push_back(TextPoint(startX - offset, startY - offset));
298         points.push_back(TextPoint(endX + offset, endY - offset));
299         points.push_back(TextPoint(endX + offset, endY + lineHeight + offset));
300         points.push_back(TextPoint(startX - offset, endY + lineHeight + offset));
301         points.push_back(TextPoint(startX - offset, endY - offset));
302         points.push_back(TextPoint(endX + offset, endY - offset));
303     } else {
304         points.push_back(TextPoint(startX - offset, startY - offset));
305         points.push_back(TextPoint(textEnd + offset, startY - offset));
306         if (textEnd - radius < endX + radius) {
307             points.push_back(TextPoint(textEnd + offset, endY + lastLineHeight_ + offset));
308         } else {
309             points.push_back(TextPoint(textEnd + offset, endY + offset));
310             points.push_back(TextPoint(endX + offset, endY + offset));
311             points.push_back(TextPoint(endX + offset, endY + lastLineHeight_ + offset));
312         }
313         points.push_back(TextPoint(textStart - offset, endY + lastLineHeight_ + offset));
314         if (startX - radius < textStart + radius) {
315             points[0] = TextPoint(textStart - offset, startY - offset);
316             points.push_back(TextPoint(textStart - offset, startY - offset));
317         } else {
318             points.push_back(TextPoint(textStart - offset, startY + lineHeight - offset));
319             points.push_back(TextPoint(startX - offset, startY + lineHeight - offset));
320             points.push_back(TextPoint(startX - offset, startY - offset));
321         }
322         points.push_back(TextPoint(textEnd + offset, startY - offset));
323     }
324 }
325 
CalculateLineAndArc(std::vector<TextPoint> & points,std::shared_ptr<RSPath> & path,float radiusRatio)326 void TextDragPattern::CalculateLineAndArc(std::vector<TextPoint>& points, std::shared_ptr<RSPath>& path,
327     float radiusRatio)
328 {
329     auto originRadius = GetDragCornerRadius().ConvertToPx();
330     auto radius = originRadius * radiusRatio;
331     path->MoveTo(points[0].x + radius, points[0].y);
332     auto frontPoint = points[0];
333     size_t step = 2;
334     for (size_t i = 0; i + step < points.size(); i++) {
335         auto firstPoint = points[i];
336         auto crossPoint = points[i + 1];
337         auto secondPoint = points[i + step];
338         float tempRadius = radius;
339         if (crossPoint.y == firstPoint.y) {
340             int directionX = (crossPoint.x - firstPoint.x) > 0 ? 1 : -1;
341             int directionY = (secondPoint.y - crossPoint.y) > 0 ? 1 : -1;
342             auto direction =
343                 (directionX * directionY > 0) ? RSPathDirection::CW_DIRECTION : RSPathDirection::CCW_DIRECTION;
344             bool isInwardFoldingCorner = frontPoint.x == firstPoint.x && firstPoint.y == crossPoint.y &&
345                 secondPoint.x == crossPoint.x;
346             isInwardFoldingCorner = isInwardFoldingCorner && (frontPoint.y - firstPoint.y) *
347                 (crossPoint.y - secondPoint.y) > 0;
348             if (isInwardFoldingCorner) {
349                 radius = std::min(std::abs(secondPoint.y - crossPoint.y) / CONSTANT_HALF, tempRadius);
350             }
351             path->LineTo(crossPoint.x - radius * directionX, crossPoint.y);
352             path->ArcTo(radius, radius, 0.0f, direction, crossPoint.x, crossPoint.y + radius * directionY);
353         } else {
354             int directionX = (secondPoint.x - crossPoint.x) > 0 ? 1 : -1;
355             int directionY = (crossPoint.y - firstPoint.y) > 0 ? 1 : -1;
356             auto direction =
357                 (directionX * directionY < 0) ? RSPathDirection::CW_DIRECTION : RSPathDirection::CCW_DIRECTION;
358             bool isInwardFoldingCorner = frontPoint.y == firstPoint.y && firstPoint.x == crossPoint.x &&
359                 secondPoint.y == crossPoint.y;
360             isInwardFoldingCorner = isInwardFoldingCorner && (frontPoint.x - firstPoint.x) *
361                 (crossPoint.x - secondPoint.x) > 0;
362             if (isInwardFoldingCorner) {
363                 radius = std::min(std::abs(firstPoint.y - crossPoint.y) / CONSTANT_HALF, tempRadius);
364             }
365             path->LineTo(crossPoint.x, crossPoint.y - radius * directionY);
366             path->ArcTo(radius, radius, 0.0f, direction, crossPoint.x + radius * directionX, secondPoint.y);
367         }
368         radius = tempRadius;
369         frontPoint = firstPoint;
370     }
371     path->MoveTo(points[0].x + radius, points[0].y);
372 }
373 
CalculateLine(std::vector<TextPoint> & points,std::shared_ptr<RSPath> & path)374 void TextDragPattern::CalculateLine(std::vector<TextPoint>& points, std::shared_ptr<RSPath>& path)
375 {
376     auto radius = GetDragCornerRadius().ConvertToPx();
377     path->MoveTo(points[0].x + radius, points[0].y);
378     size_t step = 2;
379     for (size_t i = 0; i + step < points.size(); i++) {
380         auto crossPoint = points[i + 1];
381         path->LineTo(crossPoint.x, crossPoint.y);
382     }
383 }
384 
GetDragBackgroundColor()385 Color TextDragPattern::GetDragBackgroundColor()
386 {
387     auto pipeline = PipelineContext::GetCurrentContextSafelyWithCheck();
388     CHECK_NULL_RETURN(pipeline, Color(TEXT_DRAG_COLOR_BG));
389     auto textTheme = pipeline->GetTheme<TextTheme>();
390     CHECK_NULL_RETURN(textTheme, Color(TEXT_DRAG_COLOR_BG));
391     return textTheme->GetDragBackgroundColor();
392 }
393 
GetDragCornerRadius()394 Dimension TextDragPattern::GetDragCornerRadius()
395 {
396     auto deviceType = SystemProperties::GetDeviceType();
397     if (deviceType == DeviceType::TWO_IN_ONE) {
398         return TEXT_DRAG_RADIUS_2IN1;
399     }
400     return TEXT_DRAG_RADIUS;
401 }
402 
OnDetachFromMainTree()403 void TextDragPattern::OnDetachFromMainTree()
404 {
405     ResetAnimatingParagraph();
406     CHECK_NULL_VOID(onDetachFromMainTree_);
407     onDetachFromMainTree_();
408 }
409 } // namespace OHOS::Ace::NG
410