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