OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "ui/compositor/layer_animator.h" | 5 #include "ui/compositor/layer_animator.h" |
6 | 6 |
7 #include "base/debug/trace_event.h" | 7 #include "base/debug/trace_event.h" |
8 #include "base/logging.h" | 8 #include "base/logging.h" |
9 #include "base/memory/scoped_ptr.h" | 9 #include "base/memory/scoped_ptr.h" |
| 10 #include "cc/animation_id_provider.h" |
10 #include "ui/base/animation/animation_container.h" | 11 #include "ui/base/animation/animation_container.h" |
11 #include "ui/compositor/compositor.h" | 12 #include "ui/compositor/compositor.h" |
12 #include "ui/compositor/layer.h" | 13 #include "ui/compositor/layer.h" |
13 #include "ui/compositor/layer_animation_delegate.h" | 14 #include "ui/compositor/layer_animation_delegate.h" |
14 #include "ui/compositor/layer_animation_observer.h" | 15 #include "ui/compositor/layer_animation_observer.h" |
15 #include "ui/compositor/layer_animation_sequence.h" | 16 #include "ui/compositor/layer_animation_sequence.h" |
16 | 17 |
17 #define SAFE_INVOKE_VOID(function, running_anim, ...) \ | 18 #define SAFE_INVOKE_VOID(function, running_anim, ...) \ |
18 if (running_anim.is_sequence_alive()) \ | 19 if (running_anim.is_sequence_alive()) \ |
19 function(running_anim.sequence(), ##__VA_ARGS__) | 20 function(running_anim.sequence(), ##__VA_ARGS__) |
(...skipping 175 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
195 } | 196 } |
196 | 197 |
197 // Starting a zero duration pause that affects all the animated properties | 198 // Starting a zero duration pause that affects all the animated properties |
198 // will prevent any of the sequences from animating until there are no | 199 // will prevent any of the sequences from animating until there are no |
199 // running animations that affect any of these properties, as well as | 200 // running animations that affect any of these properties, as well as |
200 // handle preemption strategy. | 201 // handle preemption strategy. |
201 StartAnimation(new LayerAnimationSequence( | 202 StartAnimation(new LayerAnimationSequence( |
202 LayerAnimationElement::CreatePauseElement(animated_properties, | 203 LayerAnimationElement::CreatePauseElement(animated_properties, |
203 base::TimeDelta()))); | 204 base::TimeDelta()))); |
204 | 205 |
| 206 bool wait_for_group_start = false; |
| 207 for (iter = animations.begin(); iter != animations.end(); ++iter) |
| 208 wait_for_group_start |= (*iter)->IsFirstElementThreaded(); |
| 209 |
| 210 int group_id = cc::AnimationIdProvider::NextGroupId(); |
| 211 |
205 // These animations (provided they don't animate any common properties) will | 212 // These animations (provided they don't animate any common properties) will |
206 // now animate together if trivially scheduled. | 213 // now animate together if trivially scheduled. |
207 for (iter = animations.begin(); iter != animations.end(); ++iter) { | 214 for (iter = animations.begin(); iter != animations.end(); ++iter) { |
| 215 (*iter)->set_animation_group_id(group_id); |
| 216 (*iter)->set_waiting_for_group_start(wait_for_group_start); |
208 ScheduleAnimation(*iter); | 217 ScheduleAnimation(*iter); |
209 } | 218 } |
210 | 219 |
211 adding_animations_ = false; | 220 adding_animations_ = false; |
212 UpdateAnimationState(); | 221 UpdateAnimationState(); |
213 } | 222 } |
214 | 223 |
215 | 224 |
216 void LayerAnimator::ScheduleTogether( | 225 void LayerAnimator::ScheduleTogether( |
217 const std::vector<LayerAnimationSequence*>& animations) { | 226 const std::vector<LayerAnimationSequence*>& animations) { |
218 scoped_refptr<LayerAnimator> retain(this); | 227 scoped_refptr<LayerAnimator> retain(this); |
219 | 228 |
220 // Collect all the affected properties. | 229 // Collect all the affected properties. |
221 LayerAnimationElement::AnimatableProperties animated_properties; | 230 LayerAnimationElement::AnimatableProperties animated_properties; |
222 std::vector<LayerAnimationSequence*>::const_iterator iter; | 231 std::vector<LayerAnimationSequence*>::const_iterator iter; |
223 for (iter = animations.begin(); iter != animations.end(); ++iter) { | 232 for (iter = animations.begin(); iter != animations.end(); ++iter) { |
224 animated_properties.insert((*iter)->properties().begin(), | 233 animated_properties.insert((*iter)->properties().begin(), |
225 (*iter)->properties().end()); | 234 (*iter)->properties().end()); |
226 } | 235 } |
227 | 236 |
228 // Scheduling a zero duration pause that affects all the animated properties | 237 // Scheduling a zero duration pause that affects all the animated properties |
229 // will prevent any of the sequences from animating until there are no | 238 // will prevent any of the sequences from animating until there are no |
230 // running animations that affect any of these properties. | 239 // running animations that affect any of these properties. |
231 ScheduleAnimation(new LayerAnimationSequence( | 240 ScheduleAnimation(new LayerAnimationSequence( |
232 LayerAnimationElement::CreatePauseElement(animated_properties, | 241 LayerAnimationElement::CreatePauseElement(animated_properties, |
233 base::TimeDelta()))); | 242 base::TimeDelta()))); |
234 | 243 |
| 244 bool wait_for_group_start = false; |
| 245 for (iter = animations.begin(); iter != animations.end(); ++iter) |
| 246 wait_for_group_start |= (*iter)->IsFirstElementThreaded(); |
| 247 |
| 248 int group_id = cc::AnimationIdProvider::NextGroupId(); |
| 249 |
235 // These animations (provided they don't animate any common properties) will | 250 // These animations (provided they don't animate any common properties) will |
236 // now animate together if trivially scheduled. | 251 // now animate together if trivially scheduled. |
237 for (iter = animations.begin(); iter != animations.end(); ++iter) { | 252 for (iter = animations.begin(); iter != animations.end(); ++iter) { |
| 253 (*iter)->set_animation_group_id(group_id); |
| 254 (*iter)->set_waiting_for_group_start(wait_for_group_start); |
238 ScheduleAnimation(*iter); | 255 ScheduleAnimation(*iter); |
239 } | 256 } |
240 | 257 |
241 UpdateAnimationState(); | 258 UpdateAnimationState(); |
242 } | 259 } |
243 | 260 |
244 void LayerAnimator::SchedulePauseForProperties( | 261 void LayerAnimator::SchedulePauseForProperties( |
245 base::TimeDelta duration, | 262 base::TimeDelta duration, |
246 LayerAnimationElement::AnimatableProperty property, | 263 LayerAnimationElement::AnimatableProperty property, |
247 ...) { | 264 ...) { |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
292 | 309 |
293 void LayerAnimator::RemoveObserver(LayerAnimationObserver* observer) { | 310 void LayerAnimator::RemoveObserver(LayerAnimationObserver* observer) { |
294 observers_.RemoveObserver(observer); | 311 observers_.RemoveObserver(observer); |
295 // Remove the observer from all sequences as well. | 312 // Remove the observer from all sequences as well. |
296 for (AnimationQueue::iterator queue_iter = animation_queue_.begin(); | 313 for (AnimationQueue::iterator queue_iter = animation_queue_.begin(); |
297 queue_iter != animation_queue_.end(); ++queue_iter) { | 314 queue_iter != animation_queue_.end(); ++queue_iter) { |
298 (*queue_iter)->RemoveObserver(observer); | 315 (*queue_iter)->RemoveObserver(observer); |
299 } | 316 } |
300 } | 317 } |
301 | 318 |
| 319 void LayerAnimator::OnThreadedAnimationStarted( |
| 320 const cc::AnimationEvent& event) { |
| 321 LayerAnimationElement::AnimatableProperty property = |
| 322 LayerAnimationElement::ToAnimatableProperty(event.targetProperty); |
| 323 |
| 324 RunningAnimation* running = GetRunningAnimation(property); |
| 325 if (!running) |
| 326 return; |
| 327 DCHECK(running->is_sequence_alive()); |
| 328 |
| 329 if (running->sequence()->animation_group_id() != event.groupId) |
| 330 return; |
| 331 |
| 332 running->sequence()->OnThreadedAnimationStarted(event); |
| 333 if (!running->sequence()->waiting_for_group_start()) |
| 334 return; |
| 335 |
| 336 base::TimeTicks start_time = base::TimeTicks::FromInternalValue( |
| 337 event.monotonicTime * base::Time::kMicrosecondsPerSecond); |
| 338 |
| 339 // The call to GetRunningAnimation made above already purged deleted |
| 340 // animations, so we are guaranteed that all the animations we iterate |
| 341 // over now are alive. |
| 342 for (RunningAnimations::iterator iter = running_animations_.begin(); |
| 343 iter != running_animations_.end(); ++iter) { |
| 344 if ((*iter).sequence()->animation_group_id() == event.groupId) { |
| 345 (*iter).sequence()->set_start_time(start_time); |
| 346 (*iter).sequence()->set_waiting_for_group_start(false); |
| 347 } |
| 348 } |
| 349 } |
| 350 |
302 // LayerAnimator protected ----------------------------------------------------- | 351 // LayerAnimator protected ----------------------------------------------------- |
303 | 352 |
304 void LayerAnimator::ProgressAnimation(LayerAnimationSequence* sequence, | 353 void LayerAnimator::ProgressAnimation(LayerAnimationSequence* sequence, |
305 base::TimeTicks now) { | 354 base::TimeTicks now) { |
306 if (!delegate()) | 355 if (!delegate() || sequence->waiting_for_group_start()) |
307 return; | 356 return; |
308 | 357 |
309 sequence->Progress(now, delegate()); | 358 sequence->Progress(now, delegate()); |
310 } | 359 } |
311 | 360 |
312 void LayerAnimator::ProgressAnimationToEnd(LayerAnimationSequence* sequence) { | 361 void LayerAnimator::ProgressAnimationToEnd(LayerAnimationSequence* sequence) { |
313 if (!delegate()) | 362 if (!delegate()) |
314 return; | 363 return; |
315 | 364 |
316 sequence->ProgressToEnd(delegate()); | 365 sequence->ProgressToEnd(delegate()); |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
392 else if (!should_start && is_started_) | 441 else if (!should_start && is_started_) |
393 GetAnimationContainer()->Stop(this); | 442 GetAnimationContainer()->Stop(this); |
394 | 443 |
395 is_started_ = should_start; | 444 is_started_ = should_start; |
396 } | 445 } |
397 | 446 |
398 LayerAnimationSequence* LayerAnimator::RemoveAnimation( | 447 LayerAnimationSequence* LayerAnimator::RemoveAnimation( |
399 LayerAnimationSequence* sequence) { | 448 LayerAnimationSequence* sequence) { |
400 linked_ptr<LayerAnimationSequence> to_return; | 449 linked_ptr<LayerAnimationSequence> to_return; |
401 | 450 |
| 451 bool is_running = false; |
| 452 |
402 // First remove from running animations | 453 // First remove from running animations |
403 for (RunningAnimations::iterator iter = running_animations_.begin(); | 454 for (RunningAnimations::iterator iter = running_animations_.begin(); |
404 iter != running_animations_.end(); ++iter) { | 455 iter != running_animations_.end(); ++iter) { |
405 if ((*iter).sequence() == sequence) { | 456 if ((*iter).sequence() == sequence) { |
406 running_animations_.erase(iter); | 457 running_animations_.erase(iter); |
| 458 is_running = true; |
407 break; | 459 break; |
408 } | 460 } |
409 } | 461 } |
410 | 462 |
411 // Then remove from the queue | 463 // Then remove from the queue |
412 for (AnimationQueue::iterator queue_iter = animation_queue_.begin(); | 464 for (AnimationQueue::iterator queue_iter = animation_queue_.begin(); |
413 queue_iter != animation_queue_.end(); ++queue_iter) { | 465 queue_iter != animation_queue_.end(); ++queue_iter) { |
414 if ((*queue_iter) == sequence) { | 466 if ((*queue_iter) == sequence) { |
415 to_return = *queue_iter; | 467 to_return = *queue_iter; |
416 animation_queue_.erase(queue_iter); | 468 animation_queue_.erase(queue_iter); |
417 break; | 469 break; |
418 } | 470 } |
419 } | 471 } |
420 | 472 |
| 473 if (!to_return.get() || |
| 474 !to_return->waiting_for_group_start() || |
| 475 !to_return->IsFirstElementThreaded()) |
| 476 return to_return.release(); |
| 477 |
| 478 // The removed sequence may have been responsible for making other sequences |
| 479 // wait for a group start. If no other sequences in the group have a |
| 480 // threaded first element, the group no longer needs the additional wait. |
| 481 bool is_wait_still_needed = false; |
| 482 int group_id = to_return->animation_group_id(); |
| 483 for (AnimationQueue::iterator queue_iter = animation_queue_.begin(); |
| 484 queue_iter != animation_queue_.end(); ++queue_iter) { |
| 485 if (((*queue_iter)->animation_group_id() == group_id) && |
| 486 (*queue_iter)->IsFirstElementThreaded()) { |
| 487 is_wait_still_needed = true; |
| 488 break; |
| 489 } |
| 490 } |
| 491 |
| 492 if (is_wait_still_needed) |
| 493 return to_return.release(); |
| 494 |
| 495 for (AnimationQueue::iterator queue_iter = animation_queue_.begin(); |
| 496 queue_iter != animation_queue_.end(); ++queue_iter) { |
| 497 if ((*queue_iter)->animation_group_id() == group_id) { |
| 498 (*queue_iter)->set_waiting_for_group_start(false); |
| 499 if (is_running) |
| 500 (*queue_iter)->set_start_time(last_step_time_); |
| 501 } |
| 502 } |
421 return to_return.release(); | 503 return to_return.release(); |
422 } | 504 } |
423 | 505 |
424 void LayerAnimator::FinishAnimation( | 506 void LayerAnimator::FinishAnimation( |
425 LayerAnimationSequence* sequence, bool abort) { | 507 LayerAnimationSequence* sequence, bool abort) { |
426 scoped_refptr<LayerAnimator> retain(this); | 508 scoped_refptr<LayerAnimator> retain(this); |
427 scoped_ptr<LayerAnimationSequence> removed(RemoveAnimation(sequence)); | 509 scoped_ptr<LayerAnimationSequence> removed(RemoveAnimation(sequence)); |
428 if (abort) | 510 if (abort) |
429 sequence->Abort(); | 511 sequence->Abort(delegate()); |
430 else | 512 else |
431 ProgressAnimationToEnd(sequence); | 513 ProgressAnimationToEnd(sequence); |
432 ProcessQueue(); | 514 ProcessQueue(); |
433 UpdateAnimationState(); | 515 UpdateAnimationState(); |
434 } | 516 } |
435 | 517 |
436 void LayerAnimator::FinishAnyAnimationWithZeroDuration() { | 518 void LayerAnimator::FinishAnyAnimationWithZeroDuration() { |
437 scoped_refptr<LayerAnimator> retain(this); | 519 scoped_refptr<LayerAnimator> retain(this); |
438 // Special case: if we've started a 0 duration animation, just finish it now | 520 // Special case: if we've started a 0 duration animation, just finish it now |
439 // and get rid of it. We need to make a copy because Progress may indirectly | 521 // and get rid of it. We need to make a copy because Progress may indirectly |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
495 RunningAnimations running_animations_copy = running_animations_; | 577 RunningAnimations running_animations_copy = running_animations_; |
496 for (size_t i = 0; i < running_animations_copy.size(); ++i) { | 578 for (size_t i = 0; i < running_animations_copy.size(); ++i) { |
497 if (!SAFE_INVOKE_BOOL(HasAnimation, running_animations_copy[i])) | 579 if (!SAFE_INVOKE_BOOL(HasAnimation, running_animations_copy[i])) |
498 continue; | 580 continue; |
499 | 581 |
500 if (running_animations_copy[i].sequence()->HasCommonProperty( | 582 if (running_animations_copy[i].sequence()->HasCommonProperty( |
501 sequence->properties())) { | 583 sequence->properties())) { |
502 scoped_ptr<LayerAnimationSequence> removed( | 584 scoped_ptr<LayerAnimationSequence> removed( |
503 SAFE_INVOKE_PTR(RemoveAnimation, running_animations_copy[i])); | 585 SAFE_INVOKE_PTR(RemoveAnimation, running_animations_copy[i])); |
504 if (abort) | 586 if (abort) |
505 running_animations_copy[i].sequence()->Abort(); | 587 running_animations_copy[i].sequence()->Abort(delegate()); |
506 else | 588 else |
507 SAFE_INVOKE_VOID(ProgressAnimationToEnd, running_animations_copy[i]); | 589 SAFE_INVOKE_VOID(ProgressAnimationToEnd, running_animations_copy[i]); |
508 } | 590 } |
509 } | 591 } |
510 | 592 |
511 // Same for the queued animations that haven't been started. Again, we'll | 593 // Same for the queued animations that haven't been started. Again, we'll |
512 // need to operate on a copy. | 594 // need to operate on a copy. |
513 std::vector<base::WeakPtr<LayerAnimationSequence> > sequences; | 595 std::vector<base::WeakPtr<LayerAnimationSequence> > sequences; |
514 for (AnimationQueue::iterator queue_iter = animation_queue_.begin(); | 596 for (AnimationQueue::iterator queue_iter = animation_queue_.begin(); |
515 queue_iter != animation_queue_.end(); ++queue_iter) | 597 queue_iter != animation_queue_.end(); ++queue_iter) |
516 sequences.push_back((*queue_iter)->AsWeakPtr()); | 598 sequences.push_back((*queue_iter)->AsWeakPtr()); |
517 | 599 |
518 for (size_t i = 0; i < sequences.size(); ++i) { | 600 for (size_t i = 0; i < sequences.size(); ++i) { |
519 if (!sequences[i] || !HasAnimation(sequences[i])) | 601 if (!sequences[i] || !HasAnimation(sequences[i])) |
520 continue; | 602 continue; |
521 | 603 |
522 if (sequences[i]->HasCommonProperty(sequence->properties())) { | 604 if (sequences[i]->HasCommonProperty(sequence->properties())) { |
523 scoped_ptr<LayerAnimationSequence> removed(RemoveAnimation(sequences[i])); | 605 scoped_ptr<LayerAnimationSequence> removed(RemoveAnimation(sequences[i])); |
524 if (abort) | 606 if (abort) |
525 sequences[i]->Abort(); | 607 sequences[i]->Abort(delegate()); |
526 else | 608 else |
527 ProgressAnimationToEnd(sequences[i]); | 609 ProgressAnimationToEnd(sequences[i]); |
528 } | 610 } |
529 } | 611 } |
530 } | 612 } |
531 | 613 |
532 void LayerAnimator::ImmediatelySetNewTarget(LayerAnimationSequence* sequence) { | 614 void LayerAnimator::ImmediatelySetNewTarget(LayerAnimationSequence* sequence) { |
533 // Need to detect if our sequence gets destroyed. | 615 // Need to detect if our sequence gets destroyed. |
534 base::WeakPtr<LayerAnimationSequence> weak_sequence_ptr = | 616 base::WeakPtr<LayerAnimationSequence> weak_sequence_ptr = |
535 sequence->AsWeakPtr(); | 617 sequence->AsWeakPtr(); |
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
677 // last_tick_time() from there to ensure animations started during the same | 759 // last_tick_time() from there to ensure animations started during the same |
678 // event complete at the same time. | 760 // event complete at the same time. |
679 base::TimeTicks start_time; | 761 base::TimeTicks start_time; |
680 if (is_animating() || adding_animations_) | 762 if (is_animating() || adding_animations_) |
681 start_time = last_step_time_; | 763 start_time = last_step_time_; |
682 else if (GetAnimationContainer()->is_running()) | 764 else if (GetAnimationContainer()->is_running()) |
683 start_time = GetAnimationContainer()->last_tick_time(); | 765 start_time = GetAnimationContainer()->last_tick_time(); |
684 else | 766 else |
685 start_time = base::TimeTicks::Now(); | 767 start_time = base::TimeTicks::Now(); |
686 | 768 |
687 sequence->set_start_time(start_time); | 769 if (!sequence->animation_group_id()) |
| 770 sequence->set_animation_group_id(cc::AnimationIdProvider::NextGroupId()); |
| 771 if (sequence->waiting_for_group_start()) |
| 772 sequence->ProgressToEffectiveStart(delegate()); |
| 773 else |
| 774 sequence->set_start_time(start_time); |
688 running_animations_.push_back( | 775 running_animations_.push_back( |
689 RunningAnimation(sequence->AsWeakPtr())); | 776 RunningAnimation(sequence->AsWeakPtr())); |
690 | 777 |
691 // Need to keep a reference to the animation. | 778 // Need to keep a reference to the animation. |
692 AddToQueueIfNotPresent(sequence); | 779 AddToQueueIfNotPresent(sequence); |
693 | 780 |
694 // Ensure that animations get stepped at their start time. | 781 // Ensure that animations get stepped at their start time. |
695 Step(start_time); | 782 Step(start_time); |
696 | 783 |
697 return true; | 784 return true; |
(...skipping 28 matching lines...) Expand all Loading... |
726 // Abort should never affect the set of running animations, but just in case | 813 // Abort should never affect the set of running animations, but just in case |
727 // clients are badly behaved, we will use a copy of the running animations. | 814 // clients are badly behaved, we will use a copy of the running animations. |
728 RunningAnimations running_animations_copy = running_animations_; | 815 RunningAnimations running_animations_copy = running_animations_; |
729 for (size_t i = 0; i < running_animations_copy.size(); ++i) { | 816 for (size_t i = 0; i < running_animations_copy.size(); ++i) { |
730 if (!SAFE_INVOKE_BOOL(HasAnimation, running_animations_copy[i])) | 817 if (!SAFE_INVOKE_BOOL(HasAnimation, running_animations_copy[i])) |
731 continue; | 818 continue; |
732 | 819 |
733 scoped_ptr<LayerAnimationSequence> removed( | 820 scoped_ptr<LayerAnimationSequence> removed( |
734 RemoveAnimation(running_animations_copy[i].sequence())); | 821 RemoveAnimation(running_animations_copy[i].sequence())); |
735 if (removed.get()) | 822 if (removed.get()) |
736 removed->Abort(); | 823 removed->Abort(delegate()); |
737 } | 824 } |
738 // This *should* have cleared the list of running animations. | 825 // This *should* have cleared the list of running animations. |
739 DCHECK(running_animations_.empty()); | 826 DCHECK(running_animations_.empty()); |
740 running_animations_.clear(); | 827 running_animations_.clear(); |
741 animation_queue_.clear(); | 828 animation_queue_.clear(); |
742 UpdateAnimationState(); | 829 UpdateAnimationState(); |
743 } | 830 } |
744 | 831 |
745 void LayerAnimator::PurgeDeletedAnimations() { | 832 void LayerAnimator::PurgeDeletedAnimations() { |
746 for (size_t i = 0; i < running_animations_.size();) { | 833 for (size_t i = 0; i < running_animations_.size();) { |
747 if (!running_animations_[i].is_sequence_alive()) | 834 if (!running_animations_[i].is_sequence_alive()) |
748 running_animations_.erase(running_animations_.begin() + i); | 835 running_animations_.erase(running_animations_.begin() + i); |
749 else | 836 else |
750 i++; | 837 i++; |
751 } | 838 } |
752 } | 839 } |
753 | 840 |
754 LayerAnimator::RunningAnimation::RunningAnimation( | 841 LayerAnimator::RunningAnimation::RunningAnimation( |
755 const base::WeakPtr<LayerAnimationSequence>& sequence) | 842 const base::WeakPtr<LayerAnimationSequence>& sequence) |
756 : sequence_(sequence) { | 843 : sequence_(sequence) { |
757 } | 844 } |
758 | 845 |
759 LayerAnimator::RunningAnimation::~RunningAnimation() { } | 846 LayerAnimator::RunningAnimation::~RunningAnimation() { } |
760 | 847 |
761 } // namespace ui | 848 } // namespace ui |
OLD | NEW |