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/select_overlay/select_overlay_modifier.h"
17
18 #include "core/components/text_overlay/text_overlay_theme.h"
19 #include "core/components_ng/render/drawing.h"
20 #include "core/pipeline_ng/pipeline_context.h"
21
22 namespace OHOS::Ace::NG {
23 namespace {
24 constexpr Dimension COORDINATE_X = 8.13_vp;
25 constexpr Dimension COORDINATE_Y = 8.13_vp;
26 constexpr Dimension MORE_ANIMATION_LINEEND_X = -8.9_vp;
27 constexpr Dimension MORE_ANIMATION_LINEEND_Y = 0.6_vp;
28 constexpr Dimension MORE_ANIMATION_OTHER_CIRCLE_X = 1.25_vp;
29 constexpr Dimension MORE_ANIMATION_OTHER_CIRCLE_Y = 8.25_vp;
30 constexpr Dimension MORE_ANIMATION_END_CIRCLE_X = 9.0_vp;
31 constexpr Dimension MORE_ANIMATION_TOP_CIRCLE_Y = -0.25_vp;
32 constexpr Dimension MASK_OFFSET_Y = 1.75_vp;
33 constexpr Dimension MASK_WIDTH = 24.0_vp;
34 constexpr Dimension MASK_HEIGHT = 10.25_vp;
35
36 constexpr int32_t ICON_MICRO_ANIMATION_DURATION1 = 300;
37 constexpr int32_t ICON_MICRO_ANIMATION_DURATION2 = 200;
38 constexpr int32_t ROUND_NUMBER = 4;
39 constexpr int32_t FIRST_INDEX = 0;
40 constexpr int32_t SECOND_INDEX = 1;
41 constexpr int32_t THIRD_INDEX = 2;
42 constexpr int32_t FOURTH_INDEX = 3;
43
44 constexpr float ROTATION_ANGLE = 45.0f;
45
46 std::vector<int32_t> circle_x { -1, 0, 1, 0 };
47 std::vector<int32_t> circle_Y { 0, -1, 0, 1 };
48 } // namespace
49
SelectOverlayModifier(const OffsetF & menuOptionOffset,bool isReverse,const WeakPtr<Pattern> & pattern)50 SelectOverlayModifier::SelectOverlayModifier(
51 const OffsetF& menuOptionOffset, bool isReverse, const WeakPtr<Pattern>& pattern)
52 : isReverse_(isReverse), pattern_(pattern)
53 {
54 pointRadius_ = AceType::MakeRefPtr<AnimatablePropertyFloat>(Dimension(1.75_vp).ConvertToPx());
55 AttachProperty(pointRadius_);
56
57 headPointRadius_ = AceType::MakeRefPtr<AnimatablePropertyFloat>(Dimension(1.75_vp).ConvertToPx());
58 AttachProperty(headPointRadius_);
59
60 menuOptionOffset_ = AceType::MakeRefPtr<PropertyOffsetF>(OffsetF());
61 AttachProperty(menuOptionOffset_);
62
63 rotationAngle_ = AceType::MakeRefPtr<AnimatablePropertyFloat>(isReverse_ ? -ROTATION_ANGLE : ROTATION_ANGLE);
64 AttachProperty(rotationAngle_);
65
66 circlesAndBackArrowOpacity_ = AceType::MakeRefPtr<AnimatablePropertyFloat>(1.0);
67 AttachProperty(circlesAndBackArrowOpacity_);
68
69 firstHandleIsShow_ = AceType::MakeRefPtr<PropertyBool>(false);
70 AttachProperty(firstHandleIsShow_);
71
72 secondHandleIsShow_ = AceType::MakeRefPtr<PropertyBool>(false);
73 AttachProperty(secondHandleIsShow_);
74
75 hasExtensionMenu_ = AceType::MakeRefPtr<PropertyBool>(false);
76 AttachProperty(hasExtensionMenu_);
77
78 SetDefaultCircleAndLineEndOffset();
79 }
80
SetDefaultCircleAndLineEndOffset()81 void SelectOverlayModifier::SetDefaultCircleAndLineEndOffset()
82 {
83 for (int32_t i = 0; i < ROUND_NUMBER; i++) {
84 auto coordinate = OffsetF(COORDINATE_X.ConvertToPx() * circle_x[i], COORDINATE_Y.ConvertToPx() * circle_Y[i]);
85 auto circleOffset = AceType::MakeRefPtr<AnimatablePropertyOffsetF>(coordinate);
86 auto lineEndCoordinate = coordinate;
87 auto lineEndOffset = AceType::MakeRefPtr<AnimatablePropertyOffsetF>(lineEndCoordinate);
88 circleOffset_.emplace_back(circleOffset);
89 if (i > FIRST_INDEX) {
90 if (i == THIRD_INDEX && isReverse_ && !circleOffset_.empty()) {
91 lineEndOffset->Set(circleOffset_.front()->Get());
92 }
93 lineEndOffset_.emplace_back(lineEndOffset);
94 if (static_cast<int32_t>(lineEndOffset_.size()) > i - 1) {
95 AttachProperty(lineEndOffset_[i - 1]);
96 }
97 }
98 if (static_cast<int32_t>(circleOffset_.size()) > i) {
99 AttachProperty(circleOffset_[i]);
100 }
101 }
102 }
103
SetOtherPointRadius(const Dimension & radius,bool noAnimation)104 void SelectOverlayModifier::SetOtherPointRadius(const Dimension& radius, bool noAnimation)
105 {
106 if (pointRadius_) {
107 AnimationOption option = AnimationOption();
108 option.SetDuration(ICON_MICRO_ANIMATION_DURATION2);
109 option.SetCurve(Curves::FRICTION);
110 auto pattern = pattern_.Upgrade();
111 auto host = pattern ? pattern->GetHost() : nullptr;
112 auto contextPtr = host ? host->GetContextRefPtr() : nullptr;
113 AnimationUtils::Animate(
114 option, [weakPointRadius = AceType::WeakClaim(AceType::RawPtr(pointRadius_)), radius]() {
115 auto pointRadius = weakPointRadius.Upgrade();
116 CHECK_NULL_VOID(pointRadius);
117 pointRadius->Set(radius.ConvertToPx());
118 }, nullptr, nullptr, contextPtr);
119 }
120 }
121
SetHeadPointRadius(const Dimension & radius,bool noAnimation)122 void SelectOverlayModifier::SetHeadPointRadius(const Dimension& radius, bool noAnimation)
123 {
124 if (headPointRadius_) {
125 AnimationOption option = AnimationOption();
126 option.SetDuration(ICON_MICRO_ANIMATION_DURATION2);
127 option.SetCurve(Curves::FRICTION);
128 auto pattern = pattern_.Upgrade();
129 auto host = pattern ? pattern->GetHost() : nullptr;
130 auto contextPtr = host ? host->GetContextRefPtr() : nullptr;
131 AnimationUtils::Animate(
132 option, [weakHeadPointRadius = AceType::WeakClaim(AceType::RawPtr(headPointRadius_)), radius]() {
133 auto headPointRadius = weakHeadPointRadius.Upgrade();
134 CHECK_NULL_VOID(headPointRadius);
135 headPointRadius->Set(radius.ConvertToPx());
136 }, nullptr, nullptr, contextPtr);
137 }
138 }
139
SetLineEndOffset(bool isMore,bool noAnimation)140 void SelectOverlayModifier::SetLineEndOffset(bool isMore, bool noAnimation)
141 {
142 if (circleOffset_.size() < ROUND_NUMBER || lineEndOffset_.size() < ROUND_NUMBER - 1) {
143 return;
144 }
145 for (int32_t i = 0; i < ROUND_NUMBER; i++) {
146 CHECK_NULL_VOID(circleOffset_[i]);
147 if (i < ROUND_NUMBER - 1) {
148 CHECK_NULL_VOID(lineEndOffset_[i]);
149 }
150 }
151 LineEndOffsetWithAnimation(isMore, noAnimation);
152 }
153
ChangeCircle()154 void SelectOverlayModifier::ChangeCircle()
155 {
156 CHECK_NULL_VOID(rotationAngle_);
157 if (circleOffset_.size() < ROUND_NUMBER || lineEndOffset_.size() < ROUND_NUMBER - 1) {
158 return;
159 }
160 if (!isReverse_) {
161 circleOffset_[FIRST_INDEX]->Set(
162 OffsetF(MORE_ANIMATION_LINEEND_X.ConvertToPx(), MORE_ANIMATION_TOP_CIRCLE_Y.ConvertToPx()));
163 circleOffset_[SECOND_INDEX]->Set(
164 OffsetF(-MORE_ANIMATION_OTHER_CIRCLE_X.ConvertToPx(), -MORE_ANIMATION_OTHER_CIRCLE_Y.ConvertToPx()));
165 circleOffset_[THIRD_INDEX]->Set(OffsetF(MORE_ANIMATION_END_CIRCLE_X.ConvertToPx(), 0));
166 circleOffset_[FOURTH_INDEX]->Set(
167 OffsetF(-MORE_ANIMATION_OTHER_CIRCLE_X.ConvertToPx(), MORE_ANIMATION_OTHER_CIRCLE_Y.ConvertToPx()));
168 lineEndOffset_[FIRST_INDEX]->Set(
169 OffsetF(MORE_ANIMATION_LINEEND_X.ConvertToPx(), -MORE_ANIMATION_LINEEND_Y.ConvertToPx()));
170 lineEndOffset_[SECOND_INDEX]->Set(
171 OffsetF(MORE_ANIMATION_LINEEND_X.ConvertToPx(), Dimension(0, DimensionUnit::VP).ConvertToPx()));
172 lineEndOffset_[THIRD_INDEX]->Set(
173 OffsetF(MORE_ANIMATION_LINEEND_X.ConvertToPx(), MORE_ANIMATION_LINEEND_Y.ConvertToPx()));
174 } else {
175 circleOffset_[FIRST_INDEX]->Set(
176 OffsetF(-MORE_ANIMATION_END_CIRCLE_X.ConvertToPx(), Dimension(0, DimensionUnit::VP).ConvertToPx()));
177 circleOffset_[SECOND_INDEX]->Set(
178 OffsetF(MORE_ANIMATION_OTHER_CIRCLE_X.ConvertToPx(), -MORE_ANIMATION_OTHER_CIRCLE_Y.ConvertToPx()));
179 circleOffset_[THIRD_INDEX]->Set(
180 OffsetF(-MORE_ANIMATION_LINEEND_X.ConvertToPx(), MORE_ANIMATION_TOP_CIRCLE_Y.ConvertToPx()));
181 circleOffset_[FOURTH_INDEX]->Set(
182 OffsetF(MORE_ANIMATION_OTHER_CIRCLE_X.ConvertToPx(), MORE_ANIMATION_OTHER_CIRCLE_Y.ConvertToPx()));
183 // Adjust the direction of back arrow when reverse layout.
184 lineEndOffset_[FIRST_INDEX]->Set(
185 OffsetF(-MORE_ANIMATION_LINEEND_X.ConvertToPx(), -MORE_ANIMATION_LINEEND_Y.ConvertToPx()));
186 lineEndOffset_[SECOND_INDEX]->Set(
187 OffsetF(-MORE_ANIMATION_LINEEND_X.ConvertToPx(), Dimension(0, DimensionUnit::VP).ConvertToPx()));
188 lineEndOffset_[THIRD_INDEX]->Set(
189 OffsetF(-MORE_ANIMATION_LINEEND_X.ConvertToPx(), MORE_ANIMATION_LINEEND_Y.ConvertToPx()));
190 }
191 rotationAngle_->Set(0);
192 }
193
LineEndOffsetWithAnimation(bool isMore,bool noAnimation)194 void SelectOverlayModifier::LineEndOffsetWithAnimation(bool isMore, bool noAnimation)
195 {
196 CHECK_NULL_VOID(rotationAngle_);
197 if (isMore) {
198 if (!noAnimation) {
199 AnimationOption option = AnimationOption();
200 option.SetDuration(ICON_MICRO_ANIMATION_DURATION1);
201 option.SetCurve(Curves::FRICTION);
202 auto pattern = pattern_.Upgrade();
203 auto host = pattern ? pattern->GetHost() : nullptr;
204 auto contextPtr = host ? host->GetContextRefPtr() : nullptr;
205 AnimationUtils::Animate(option, [weak = AceType::WeakClaim(this)]() {
206 auto overlayModifier = weak.Upgrade();
207 CHECK_NULL_VOID(overlayModifier);
208 overlayModifier->ChangeCircle();
209 }, nullptr, nullptr, contextPtr);
210 } else {
211 ChangeCircle();
212 }
213 } else {
214 BackArrowTransitionAnimation(noAnimation);
215 }
216 }
217
BackArrowTransitionChange(const OffsetF & coordinate,int32_t i)218 void SelectOverlayModifier::BackArrowTransitionChange(const OffsetF& coordinate, int32_t i)
219 {
220 if (static_cast<int32_t>(circleOffset_.size()) < i || static_cast<int32_t>(lineEndOffset_.size()) < i - 1) {
221 return;
222 }
223 circleOffset_[i]->Set(coordinate);
224 rotationAngle_->Set(isReverse_ ? -ROTATION_ANGLE : ROTATION_ANGLE);
225 if (i > FIRST_INDEX) {
226 if (i == THIRD_INDEX && isReverse_) {
227 auto endCircleOffset = OffsetF(COORDINATE_X.ConvertToPx() * circle_x[FIRST_INDEX],
228 COORDINATE_Y.ConvertToPx() * circle_Y[FIRST_INDEX]);
229 lineEndOffset_[i - 1]->Set(endCircleOffset);
230 circleOffset_[FIRST_INDEX]->Set(endCircleOffset);
231 return;
232 }
233 lineEndOffset_[i - 1]->Set(coordinate);
234 };
235 }
236
BackArrowTransitionAnimation(bool noAnimation)237 void SelectOverlayModifier::BackArrowTransitionAnimation(bool noAnimation)
238 {
239 CHECK_NULL_VOID(rotationAngle_);
240 if (!noAnimation) {
241 AnimationOption option = AnimationOption();
242 option.SetDuration(ICON_MICRO_ANIMATION_DURATION1);
243 option.SetCurve(Curves::FRICTION);
244 auto pattern = pattern_.Upgrade();
245 auto host = pattern ? pattern->GetHost() : nullptr;
246 auto contextPtr = host ? host->GetContextRefPtr() : nullptr;
247
248 for (int32_t i = 0; i < ROUND_NUMBER; i++) {
249 auto coordinate =
250 OffsetF(COORDINATE_X.ConvertToPx() * circle_x[i], COORDINATE_Y.ConvertToPx() * circle_Y[i]);
251 AnimationUtils::Animate(
252 option, [weak = AceType::WeakClaim(this),
253 weakRotationAngle = AceType::WeakClaim(AceType::RawPtr(rotationAngle_)), i, coordinate]() {
254 auto overlayModifier = weak.Upgrade();
255 CHECK_NULL_VOID(overlayModifier);
256 overlayModifier->BackArrowTransitionChange(coordinate, i);
257 }, nullptr, nullptr, contextPtr);
258 }
259 } else {
260 for (int32_t i = 0; i < ROUND_NUMBER; i++) {
261 auto coordinate =
262 OffsetF(COORDINATE_X.ConvertToPx() * circle_x[i], COORDINATE_Y.ConvertToPx() * circle_Y[i]);
263 BackArrowTransitionChange(coordinate, i);
264 }
265 }
266 }
267
onDraw(DrawingContext & drawingContext)268 void SelectOverlayModifier::onDraw(DrawingContext& drawingContext)
269 {
270 CHECK_NULL_VOID(hasExtensionMenu_);
271 CHECK_NULL_VOID(hasExtensionMenu_->Get());
272 for (int32_t i = 0; i < ROUND_NUMBER; i++) {
273 CHECK_NULL_VOID(circleOffset_[i]);
274 if (i < ROUND_NUMBER - 1) {
275 CHECK_NULL_VOID(lineEndOffset_[i]);
276 }
277 }
278 CHECK_NULL_VOID(rotationAngle_);
279 CHECK_NULL_VOID(menuOptionOffset_);
280 CHECK_NULL_VOID(pointRadius_);
281 CHECK_NULL_VOID(headPointRadius_);
282 CHECK_NULL_VOID(firstHandleIsShow_);
283 CHECK_NULL_VOID(secondHandleIsShow_);
284
285 if (!isNewAvoid_ && !firstHandleIsShow_->Get() && !secondHandleIsShow_->Get()) {
286 return;
287 }
288
289 if (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)) {
290 return;
291 }
292
293 auto pipeline = PipelineContext::GetCurrentContextSafelyWithCheck();
294 CHECK_NULL_VOID(pipeline);
295 auto textOverlayTheme = pipeline->GetTheme<TextOverlayTheme>();
296 CHECK_NULL_VOID(textOverlayTheme);
297 iconColor_ = textOverlayTheme->GetMoreOrBackIconColor();
298 DrawbBackArrow(drawingContext);
299 DrawbCircles(drawingContext);
300 }
301
DrawbBackArrow(DrawingContext & drawingContext)302 void SelectOverlayModifier::DrawbBackArrow(DrawingContext& drawingContext)
303 {
304 auto& canvas = drawingContext.canvas;
305 // Draw a back arrow.
306 canvas.Save();
307 canvas.Rotate(rotationAngle_->Get(), menuOptionOffset_->Get().GetX(), menuOptionOffset_->Get().GetY());
308
309 Color iconColor = iconColor_;
310 iconColor = iconColor.BlendOpacity(circlesAndBackArrowOpacity_->Get());
311 int32_t headPointIndex = isReverse_ ? THIRD_INDEX : FIRST_INDEX;
312 if (circleOffset_.size() < ROUND_NUMBER || lineEndOffset_.size() < ROUND_NUMBER - 1) {
313 return;
314 }
315 for (int32_t i = 0; i < ROUND_NUMBER - 2; i++) {
316 RSPen pen;
317 pen.SetAntiAlias(true);
318 pen.SetColor(iconColor.GetValue());
319 pen.SetWidth(pointRadius_->Get() * 2);
320 pen.SetCapStyle(RSPen::CapStyle::ROUND_CAP);
321 canvas.AttachPen(pen);
322 int32_t targetIndex = (i + 1 == headPointIndex ? FIRST_INDEX : i + 1);
323 auto coordinate = menuOptionOffset_->Get() + circleOffset_[targetIndex]->Get();
324 auto endOffset = menuOptionOffset_->Get() + lineEndOffset_[i]->Get();
325 canvas.DrawLine({ coordinate.GetX(), coordinate.GetY() }, { endOffset.GetX(), endOffset.GetY() });
326 canvas.DetachPen();
327 }
328
329 auto sideWidth = MASK_WIDTH.ConvertToPx();
330 auto maskOffset = menuOptionOffset_->Get() + OffsetF(-sideWidth / 2.0, MASK_OFFSET_Y.ConvertToPx());
331 RSRect clipInnerRect = RSRect(maskOffset.GetX(), maskOffset.GetY(), sideWidth + maskOffset.GetX(),
332 maskOffset.GetY() + MASK_HEIGHT.ConvertToPx());
333 canvas.ClipRect(clipInnerRect, RSClipOp::INTERSECT);
334 RSPen pen;
335 pen.SetAntiAlias(true);
336 pen.SetColor(iconColor.GetValue());
337 pen.SetWidth(pointRadius_->Get() * 2);
338 pen.SetCapStyle(RSPen::CapStyle::ROUND_CAP);
339 canvas.AttachPen(pen);
340 auto coordinate = menuOptionOffset_->Get() + circleOffset_[3]->Get();
341 auto endOffset = menuOptionOffset_->Get() + lineEndOffset_[2]->Get();
342 canvas.DrawLine({ coordinate.GetX(), coordinate.GetY() }, { endOffset.GetX(), endOffset.GetY() });
343 canvas.DetachPen();
344 canvas.Restore();
345 }
346
DrawbCircles(DrawingContext & drawingContext)347 void SelectOverlayModifier::DrawbCircles(DrawingContext& drawingContext)
348 {
349 auto& canvas = drawingContext.canvas;
350 // Paint other circles.
351 Color iconColor = iconColor_;
352 iconColor = iconColor.BlendOpacity(circlesAndBackArrowOpacity_->Get());
353 if (circleOffset_.size() < ROUND_NUMBER) {
354 return;
355 }
356 for (int32_t i = 0; i < ROUND_NUMBER; i++) {
357 canvas.Save();
358 canvas.Rotate(rotationAngle_->Get(), menuOptionOffset_->Get().GetX(), menuOptionOffset_->Get().GetY());
359 auto coordinate = menuOptionOffset_->Get() + circleOffset_[i]->Get();
360 canvas.Translate(coordinate.GetX(), coordinate.GetY());
361 RSBrush brush;
362 brush.SetAntiAlias(true);
363 brush.SetColor(iconColor.GetValue());
364 canvas.AttachBrush(brush);
365 // The radius UX effect of the top circle is different from other circles.
366 // the top circle is the third index when reverse layout.
367 if ((!isReverse_ && (i == FIRST_INDEX)) || (isReverse_ && (i == THIRD_INDEX))) {
368 canvas.DrawCircle({ 0.0, 0.0 }, headPointRadius_->Get());
369 } else {
370 canvas.DrawCircle({ 0.0, 0.0 }, pointRadius_->Get());
371 }
372 canvas.DetachBrush();
373 canvas.Restore();
374 }
375 }
376 } // namespace OHOS::Ace::NG
377