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