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