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 "base/utils/utils.h"
19 #include "core/components_ng/pattern/text/text_pattern.h"
20 #include "core/components_ng/pattern/text_drag/text_drag_base.h"
21 #include "core/components_ng/render/drawing.h"
22 #include "core/components_v2/inspector/inspector_constants.h"
23
24 namespace {
25 // uncertainty range when comparing selectedTextBox to contentRect
26 constexpr float BOX_EPSILON = 0.2f;
27 } // namespace
28
29 namespace OHOS::Ace::NG {
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & dirty,const DirtySwapConfig & config)30 bool TextDragPattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& dirty, const DirtySwapConfig& config)
31 {
32 return true;
33 }
34
GetFirstBoxRect(const std::vector<RectF> & boxes,const RectF & contentRect,const float textStartY)35 const RectF GetFirstBoxRect(const std::vector<RectF>& boxes, const RectF& contentRect, const float textStartY)
36 {
37 for (const auto& box : boxes) {
38 if (box.Bottom() + textStartY > contentRect.Top() + BOX_EPSILON) {
39 return box;
40 }
41 }
42 return boxes.front();
43 } // Obtains the first line in the visible area of the text box, including the truncated part.
44
GetLastBoxRect(const std::vector<RectF> & boxes,const RectF & contentRect,const float textStartY)45 const RectF GetLastBoxRect(const std::vector<RectF>& boxes, const RectF& contentRect, const float textStartY)
46 {
47 bool hasResult = false;
48 RectF result;
49 for (const auto& box : boxes) {
50 if (box.Bottom() + textStartY >= contentRect.Bottom() && !hasResult) {
51 result = box;
52 hasResult = true;
53 continue;
54 }
55 if (hasResult && box.Bottom() == result.Bottom()) {
56 result = box;
57 } else if (hasResult && box.Bottom() != result.Bottom()) {
58 return result;
59 }
60 }
61 return boxes.back();
62 } // Obtains the last line in the visible area of the text box, including the truncated part.
63
CreateDragNode(const RefPtr<FrameNode> & hostNode)64 RefPtr<FrameNode> TextDragPattern::CreateDragNode(const RefPtr<FrameNode>& hostNode)
65 {
66 CHECK_NULL_RETURN(hostNode, nullptr);
67 auto hostPattern = hostNode->GetPattern<TextDragBase>();
68 const auto nodeId = ElementRegister::GetInstance()->MakeUniqueId();
69 auto dragNode = FrameNode::GetOrCreateFrameNode(
70 V2::TEXTDRAG_ETS_TAG, nodeId, []() { return AceType::MakeRefPtr<TextDragPattern>(); });
71 auto dragContext = dragNode->GetRenderContext();
72 auto hostContext = hostNode->GetRenderContext();
73 if (hostContext->HasForegroundColor()) {
74 dragContext->UpdateForegroundColor(hostContext->GetForegroundColor().value());
75 }
76 if (hostContext->HasForegroundColorStrategy()) {
77 dragContext->UpdateForegroundColorStrategy(hostContext->GetForegroundColorStrategy().value());
78 }
79 auto dragPattern = dragNode->GetPattern<TextDragPattern>();
80 auto data = CalculateTextDragData(hostPattern, dragNode);
81 dragPattern->Initialize(hostPattern->GetDragParagraph(), data);
82 dragPattern->SetLastLineHeight(data.lineHeight_);
83
84 CalcSize size(NG::CalcLength(dragPattern->GetFrameWidth()), NG::CalcLength(dragPattern->GetFrameHeight()));
85 dragNode->GetLayoutProperty()->UpdateUserDefinedIdealSize(size);
86 return dragNode;
87 }
88
CalculateTextDragData(RefPtr<TextDragBase> & hostPattern,RefPtr<FrameNode> & dragNode)89 TextDragData TextDragPattern::CalculateTextDragData(RefPtr<TextDragBase>& hostPattern, RefPtr<FrameNode>& dragNode)
90 {
91 auto dragContext = dragNode->GetRenderContext();
92 auto dragPattern = dragNode->GetPattern<TextDragPattern>();
93 float textStartX = hostPattern->GetTextRect().GetX();
94 float textStartY =
95 hostPattern->IsTextArea() ? hostPattern->GetTextRect().GetY() : hostPattern->GetTextContentRect().GetY();
96 auto contentRect = hostPattern->GetTextContentRect();
97 float lineHeight = hostPattern->GetLineHeight();
98 float minWidth = TEXT_DRAG_MIN_WIDTH.ConvertToPx();
99 float bothOffset = TEXT_DRAG_OFFSET.ConvertToPx() * 2; // 2 : double
100 auto boxes = hostPattern->GetTextBoxes();
101 CHECK_NULL_RETURN(!boxes.empty(), {});
102 auto boxFirst = GetFirstBoxRect(boxes, contentRect, textStartY);
103 auto boxLast = GetLastBoxRect(boxes, contentRect, textStartY);
104 float leftHandleX = boxFirst.Left() + textStartX;
105 float leftHandleY = boxFirst.Top() + textStartY;
106 float rightHandleX = boxLast.Right() + textStartX;
107 float rightHandleY = boxLast.Top() + textStartY;
108 float lastLineHeight = boxLast.Height();
109 bool oneLineSelected = (leftHandleY == rightHandleY);
110 if (oneLineSelected) {
111 if (leftHandleX < contentRect.Left()) {
112 leftHandleX = contentRect.Left();
113 }
114 if (rightHandleX > contentRect.Right()) {
115 rightHandleX = contentRect.Right();
116 }
117 }
118 auto hostGlobalOffset = hostPattern->GetParentGlobalOffset();
119 float width = rightHandleX - leftHandleX;
120 float height = rightHandleY - leftHandleY + lastLineHeight;
121 float globalX = leftHandleX + hostGlobalOffset.GetX() - TEXT_DRAG_OFFSET.ConvertToPx();
122 float globalY = leftHandleY + hostGlobalOffset.GetY() - TEXT_DRAG_OFFSET.ConvertToPx();
123 if (oneLineSelected) {
124 float delta = 0.0f;
125 if (rightHandleX - leftHandleX + bothOffset < minWidth) {
126 delta = minWidth - (rightHandleX - leftHandleX + bothOffset);
127 width += delta;
128 globalX -= delta / 2; // 2 : half
129 }
130
131 dragPattern->SetContentOffset(
132 OffsetF(boxes.front().Left() - TEXT_DRAG_OFFSET.ConvertToPx() - delta / 2, // 2 : half
133 boxes.front().Top() - TEXT_DRAG_OFFSET.ConvertToPx()));
134 } else {
135 globalX = contentRect.Left() + hostGlobalOffset.GetX() - TEXT_DRAG_OFFSET.ConvertToPx();
136 width = contentRect.Width();
137 dragPattern->SetContentOffset(
138 OffsetF(0 - TEXT_DRAG_OFFSET.ConvertToPx(), boxes.front().Top() - TEXT_DRAG_OFFSET.ConvertToPx()));
139 }
140 dragContext->UpdatePosition(OffsetT<Dimension>(Dimension(globalX), Dimension(globalY)));
141 RectF dragTextRect(
142 textStartX + hostGlobalOffset.GetX() - globalX, textStartY + hostGlobalOffset.GetY() - globalY, width, height);
143 SelectPositionInfo info(leftHandleX + hostGlobalOffset.GetX() - globalX,
144 leftHandleY + hostGlobalOffset.GetY() - globalY, rightHandleX + hostGlobalOffset.GetX() - globalX,
145 rightHandleY + hostGlobalOffset.GetY() - globalY);
146 TextDragData data(dragTextRect, width + bothOffset, height + bothOffset, lineHeight, info, oneLineSelected);
147 return data;
148 }
149
GenerateClipPath()150 std::shared_ptr<RSPath> TextDragPattern::GenerateClipPath()
151 {
152 std::shared_ptr<RSPath> path = std::make_shared<RSPath>();
153 auto selectPosition = GetSelectPosition();
154 float startX = selectPosition.startX_;
155 float startY = selectPosition.startY_;
156 float endX = selectPosition.endX_;
157 float endY = selectPosition.endY_;
158 float textStart = GetTextRect().GetX();
159 float textEnd = textStart + GetTextRect().Width();
160 auto lineHeight = GetLineHeight();
161 if (OneLineSelected()) {
162 path->MoveTo(startX, endY);
163 path->LineTo(endX, endY);
164 path->LineTo(endX, endY + lineHeight);
165 path->LineTo(startX, endY + lineHeight);
166 path->LineTo(startX, endY);
167 } else {
168 path->MoveTo(startX, startY);
169 path->LineTo(textEnd, startY);
170 path->LineTo(textEnd, endY);
171 path->LineTo(endX, endY);
172 path->LineTo(endX, endY + lastLineHeight_);
173 path->LineTo(textStart, endY + lastLineHeight_);
174 path->LineTo(textStart, startY + lineHeight);
175 path->LineTo(startX, startY + lineHeight);
176 path->LineTo(startX, startY);
177 }
178 return path;
179 }
180
GenerateBackgroundPath(float offset)181 std::shared_ptr<RSPath> TextDragPattern::GenerateBackgroundPath(float offset)
182 {
183 std::shared_ptr<RSPath> path = std::make_shared<RSPath>();
184 std::vector<TextPoint> points;
185 GenerateBackgroundPoints(points, offset);
186 CalculateLineAndArc(points, path);
187 return path;
188 }
189
GenerateBackgroundPoints(std::vector<TextPoint> & points,float offset)190 void TextDragPattern::GenerateBackgroundPoints(std::vector<TextPoint>& points, float offset)
191 {
192 auto radius = TEXT_DRAG_RADIUS.ConvertToPx();
193 auto bothOffset = offset * 2; // 2 : double
194 auto minWidth = TEXT_DRAG_MIN_WIDTH.ConvertToPx();
195 auto selectPosition = GetSelectPosition();
196 float startX = selectPosition.startX_;
197 float startY = selectPosition.startY_;
198 float endX = selectPosition.endX_;
199 float endY = selectPosition.endY_;
200 float textStart = GetTextRect().GetX();
201 float textEnd = textStart + GetTextRect().Width();
202 auto lineHeight = GetLineHeight();
203 if (OneLineSelected()) {
204 if ((endX - startX) + bothOffset < minWidth) {
205 float delta = minWidth - ((endX - startX) + bothOffset);
206 startX -= delta / 2.0f; // 2 : half
207 endX += delta / 2.0f; // 2 : half
208 }
209 points.push_back(TextPoint(startX - offset, startY - offset));
210 points.push_back(TextPoint(endX + offset, endY - offset));
211 points.push_back(TextPoint(endX + offset, endY + lineHeight + offset));
212 points.push_back(TextPoint(startX - offset, endY + lineHeight + offset));
213 points.push_back(TextPoint(startX - offset, endY - offset));
214 points.push_back(TextPoint(endX + offset, endY - offset));
215 } else {
216 points.push_back(TextPoint(startX - offset, startY - offset));
217 points.push_back(TextPoint(textEnd + offset, startY - offset));
218 if (textEnd - radius < endX + radius) {
219 points.push_back(TextPoint(textEnd + offset, endY + lastLineHeight_ + offset));
220 } else {
221 points.push_back(TextPoint(textEnd + offset, endY + offset));
222 points.push_back(TextPoint(endX + offset, endY + offset));
223 points.push_back(TextPoint(endX + offset, endY + lastLineHeight_ + offset));
224 }
225 points.push_back(TextPoint(textStart - offset, endY + lastLineHeight_ + offset));
226 if (startX - radius < textStart + radius) {
227 points[0] = TextPoint(textStart - offset, startY - offset);
228 points.push_back(TextPoint(textStart - offset, startY - offset));
229 } else {
230 points.push_back(TextPoint(textStart - offset, startY + lineHeight - offset));
231 points.push_back(TextPoint(startX - offset, startY + lineHeight - offset));
232 points.push_back(TextPoint(startX - offset, startY - offset));
233 }
234 points.push_back(TextPoint(textEnd + offset, startY - offset));
235 }
236 }
237
CalculateLineAndArc(std::vector<TextPoint> & points,std::shared_ptr<RSPath> & path)238 void TextDragPattern::CalculateLineAndArc(std::vector<TextPoint>& points, std::shared_ptr<RSPath>& path)
239 {
240 auto radius = TEXT_DRAG_RADIUS.ConvertToPx();
241 path->MoveTo(points[0].x + radius, points[0].y);
242 size_t step = 2;
243 for (size_t i = 0; i + step < points.size(); i++) {
244 auto firstPoint = points[i];
245 auto crossPoint = points[i + 1];
246 auto secondPoint = points[i + step];
247
248 if (crossPoint.y == firstPoint.y) {
249 int directionX = (crossPoint.x - firstPoint.x) > 0 ? 1 : -1;
250 int directionY = (secondPoint.y - crossPoint.y) > 0 ? 1 : -1;
251 auto direction =
252 (directionX * directionY > 0) ? RSPathDirection::CW_DIRECTION : RSPathDirection::CCW_DIRECTION;
253 path->LineTo(crossPoint.x - radius * directionX, crossPoint.y);
254 path->ArcTo(radius, radius, 0.0f, direction, crossPoint.x, crossPoint.y + radius * directionY);
255 } else {
256 int directionX = (secondPoint.x - crossPoint.x) > 0 ? 1 : -1;
257 int directionY = (crossPoint.y - firstPoint.y) > 0 ? 1 : -1;
258 auto direction =
259 (directionX * directionY < 0) ? RSPathDirection::CW_DIRECTION : RSPathDirection::CCW_DIRECTION;
260 path->LineTo(crossPoint.x, crossPoint.y - radius * directionY);
261 path->ArcTo(radius, radius, 0.0f, direction, crossPoint.x + radius * directionX, secondPoint.y);
262 }
263 }
264 }
265 } // namespace OHOS::Ace::NG
266