Chromium Code Reviews| Index: ash/autoclick/autoclick_ring_handler.cc |
| diff --git a/ash/autoclick/autoclick_ring_handler.cc b/ash/autoclick/autoclick_ring_handler.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..6f44d43d6d9021b70ec3985c9ae0a25dfd138153 |
| --- /dev/null |
| +++ b/ash/autoclick/autoclick_ring_handler.cc |
| @@ -0,0 +1,337 @@ |
| +// Copyright (c) 2016 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "ash/autoclick/autoclick_ring_handler.h" |
| + |
| +#include <memory> |
| + |
| +#include "ash/root_window_controller.h" |
| +#include "ash/shell.h" |
| +#include "ash/shell_window_ids.h" |
| +#include "ash/wm/aura/wm_window_aura.h" |
| +#include "ash/wm/common/root_window_finder.h" |
| +#include "third_party/skia/include/core/SkColor.h" |
| +#include "third_party/skia/include/core/SkPaint.h" |
| +#include "third_party/skia/include/core/SkPath.h" |
| +#include "third_party/skia/include/core/SkRect.h" |
| +#include "ui/aura/client/screen_position_client.h" |
| +#include "ui/aura/env.h" |
| +#include "ui/aura/window.h" |
| +#include "ui/aura/window_event_dispatcher.h" |
| +#include "ui/compositor/layer.h" |
| +#include "ui/gfx/canvas.h" |
| +#include "ui/gfx/transform.h" |
| +#include "ui/views/view.h" |
| +#include "ui/views/widget/widget.h" |
| +#include "ui/wm/core/coordinate_conversion.h" |
| + |
| +namespace ash { |
| +namespace { |
| + |
| +const int kAutoclickRingOuterRadius = 30; |
| +const int kAutoclickRingInnerRadius = 20; |
| + |
| +// Angles from x-axis at which the outer and inner circles start. |
| +// const int kAutoclickRingOuterStartAngle = 0; |
| +const int kAutoclickRingInnerStartAngle = -90; |
| + |
| +const int kAutoclickRingGlowWidth = 20; |
| +// The following is half width to avoid division by 2. |
| +const int kAutoclickRingArcWidth = 2; |
| + |
| +// Start and end values for various animations. |
| +const double kAutoclickRingScaleStartValue = 1.0; |
| +const double kAutoclickRingScaleEndValue = 0.5; |
| +const double kAutoclickRingShrinkScaleEndValue = 0.5; |
| +const double kAutoclickRingOpacityStartValue = 0.1; |
| +const double kAutoclickRingOpacityEndValue = 0.5; |
| +const int kAutoclickRingAngleStartValue = -90; |
| +// The sweep angle is a bit greater than 360 to make sure the circle |
| +// completes at the end of the animation. |
| +const int kAutoclickRingAngleEndValue = 360; |
| + |
| +// Visual constants. |
| +const SkColor kAutoclickRingArcColor = SkColorSetARGB(255, 0, 255, 0); |
| +const SkColor kAutoclickRingCircleColor = SkColorSetARGB(255, 0, 0, 255); |
| +const int kAutoclickRingFrameRateHz = 60; |
| + |
| +views::Widget* CreateAutoclickRingWidget(aura::Window* root_window) { |
| + views::Widget* widget = new views::Widget; |
| + views::Widget::InitParams params; |
| + params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS; |
| + params.keep_on_top = true; |
| + params.accept_events = false; |
| + params.activatable = views::Widget::InitParams::ACTIVATABLE_NO; |
| + params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| + params.context = root_window; |
| + params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; |
| + widget->Init(params); |
| + widget->SetOpacity(0xFF); |
| + GetRootWindowController(root_window) |
| + ->GetContainer(kShellWindowId_OverlayContainer) |
| + ->AddChild(widget->GetNativeWindow()); |
| + return widget; |
| +} |
| + |
| +void PaintAutoclickRingCircle(gfx::Canvas* canvas, |
| + gfx::Point& center, |
| + int radius) { |
| + SkPaint paint; |
| + paint.setStyle(SkPaint::kStroke_Style); |
| + paint.setStrokeWidth(2 * kAutoclickRingArcWidth); |
| + paint.setColor(kAutoclickRingCircleColor); |
| + paint.setAntiAlias(true); |
| + |
| + canvas->DrawCircle(center, radius, paint); |
| +} |
| +void PaintAutoclickRingArc(gfx::Canvas* canvas, |
| + gfx::Point& center, |
| + int radius, |
| + int start_angle, |
| + int end_angle) { |
| + SkPaint paint; |
| + paint.setStyle(SkPaint::kStroke_Style); |
| + paint.setStrokeWidth(2 * kAutoclickRingArcWidth); |
| + paint.setColor(kAutoclickRingArcColor); |
| + paint.setAntiAlias(true); |
| + |
| + SkPath arc_path; |
| + arc_path.addArc(SkRect::MakeXYWH(center.x() - radius, center.y() - radius, |
| + 2 * radius, 2 * radius), |
| + start_angle, end_angle - start_angle); |
| + canvas->DrawPath(arc_path, paint); |
| +} |
| +} // namespace |
| + |
| +// View of the LongPressAutoclickRingHandler. Draws the actual contents and |
| +// updates as the animation proceeds. It also maintains the views::Widget that |
| +// the animation is shown in. |
| +class LongPressAutoclickRingHandler::LongPressAutoclickRingView |
| + : public views::View { |
| + public: |
| + LongPressAutoclickRingView(const gfx::Point& event_location, |
| + aura::Window* root_window) |
| + : views::View(), |
| + widget_(CreateAutoclickRingWidget(root_window)), |
| + current_angle_(kAutoclickRingAngleStartValue), |
| + current_scale_(kAutoclickRingScaleStartValue) { |
| + widget_->SetContentsView(this); |
| + widget_->SetAlwaysOnTop(true); |
| + |
| + // We are owned by the LongPressAutoclickRing. |
| + set_owned_by_client(); |
| + SetNewLocation(event_location, root_window); |
| + } |
| + |
| + ~LongPressAutoclickRingView() override {} |
| + |
| + void SetNewLocation(const gfx::Point& new_event_location, |
| + aura::Window* root_window) { |
| + gfx::Point point = new_event_location; |
| + aura::client::GetScreenPositionClient(root_window) |
| + ->ConvertPointToScreen(root_window, &point); |
| + widget_->SetBounds(gfx::Rect( |
| + point.x() - (kAutoclickRingOuterRadius + kAutoclickRingGlowWidth), |
| + point.y() - (kAutoclickRingOuterRadius + kAutoclickRingGlowWidth), |
| + GetPreferredSize().width(), GetPreferredSize().height())); |
| + widget_->Show(); |
| + widget_->GetNativeView()->layer()->SetOpacity( |
| + kAutoclickRingOpacityStartValue); |
| + } |
| + |
| + void UpdateWithGrowAnimation(gfx::Animation* animation) { |
| + // Update the portion of the circle filled so far and re-draw. |
| + current_angle_ = animation->CurrentValueBetween( |
| + kAutoclickRingInnerStartAngle, kAutoclickRingAngleEndValue); |
| + current_scale_ = animation->CurrentValueBetween( |
| + kAutoclickRingScaleStartValue, kAutoclickRingScaleEndValue); |
| + widget_->GetNativeView()->layer()->SetOpacity( |
| + animation->CurrentValueBetween(kAutoclickRingOpacityStartValue, |
| + kAutoclickRingOpacityEndValue)); |
| + SchedulePaint(); |
| + } |
| + |
| + void UpdateWithShrinkAnimation(gfx::Animation* animation) { |
| + current_scale_ = animation->CurrentValueBetween( |
| + kAutoclickRingScaleEndValue, kAutoclickRingShrinkScaleEndValue); |
| + widget_->GetNativeView()->layer()->SetOpacity( |
| + animation->CurrentValueBetween(kAutoclickRingOpacityEndValue, |
| + kAutoclickRingOpacityStartValue)); |
| + SchedulePaint(); |
| + } |
| + |
| + private: |
| + // Overridden from views::View. |
| + gfx::Size GetPreferredSize() const override { |
| + return gfx::Size(2 * (kAutoclickRingOuterRadius + kAutoclickRingGlowWidth), |
| + 2 * (kAutoclickRingOuterRadius + kAutoclickRingGlowWidth)); |
| + } |
| + |
| + void OnPaint(gfx::Canvas* canvas) override { |
| + gfx::Point center(GetPreferredSize().width() / 2, |
| + GetPreferredSize().height() / 2); |
| + canvas->Save(); |
| + |
| + gfx::Transform scale; |
| + scale.Scale(current_scale_, current_scale_); |
| + // We want to scale from the center. |
| + canvas->Translate(center.OffsetFromOrigin()); |
| + canvas->Transform(scale); |
| + canvas->Translate(-center.OffsetFromOrigin()); |
| + |
| + // Paint inner circle. |
| + PaintAutoclickRingArc(canvas, center, kAutoclickRingInnerRadius, |
| + kAutoclickRingInnerStartAngle, current_angle_); |
| + // Paint outer circle. |
| + PaintAutoclickRingCircle(canvas, center, kAutoclickRingOuterRadius); |
| + |
| + canvas->Restore(); |
| + } |
| + |
| + std::unique_ptr<views::Widget> widget_; |
| + int current_angle_; |
| + double current_scale_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(LongPressAutoclickRingView); |
| +}; |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| +// LongPressAutoclickRingHandler, public |
| + |
| +LongPressAutoclickRingHandler::LongPressAutoclickRingHandler() |
| + : gfx::LinearAnimation(kAutoclickRingFrameRateHz, NULL), |
|
jdufault
2016/05/31 19:06:15
Replace all NULL occurrences in this file with nul
sammiequon
2016/06/03 21:34:31
Done.
|
| + tap_down_target_(NULL), |
| + current_animation_type_(NONE) {} |
| + |
| +LongPressAutoclickRingHandler::~LongPressAutoclickRingHandler() { |
| + StopAutoclickRing(); |
| +} |
| + |
| +void LongPressAutoclickRingHandler::ProcessEvent(ui::LocatedEvent* event, |
|
jdufault
2016/05/31 19:06:15
Does |event| ever get used? Can it be removed?
It
sammiequon
2016/06/03 21:34:31
Done. Replaced with the suggested functions.
|
| + int delay_ms, |
| + bool start_animation) { |
| + aura::Window* target = GetTargetWindow(); |
| + |
| + if (tap_down_target_ && tap_down_target_ != target) |
| + return; |
| + if (start_animation) { |
| + // Start timer that will start animation on "semi-long-press". |
| + StopAutoclickRing(); |
| + SetTapDownLocationAndTarget(); |
| + current_animation_type_ = GROW_ANIMATION; |
| + StartAnimation(delay_ms); |
| + } else { |
| + StopAutoclickRing(); |
| + } |
| +} |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| +// LongPressAutoclickRingHandler, private |
| +gfx::Point LongPressAutoclickRingHandler::GetLastMouseLocation() { |
| + return aura::Env::GetInstance()->last_mouse_location(); |
| +} |
| + |
| +aura::Window* LongPressAutoclickRingHandler::GetTargetWindow() { |
| + gfx::Point screen_location = GetLastMouseLocation(); |
| + aura::Window* target = |
| + wm::WmWindowAura::GetAuraWindow(wm::GetRootWindowAt(screen_location)); |
| + DCHECK(target) << "Root window not found while rendering autoclick circle;"; |
| + return target; |
| +} |
| + |
| +void LongPressAutoclickRingHandler::SetTapDownLocationAndTarget() { |
| + gfx::Point screen_location = GetLastMouseLocation(); |
| + gfx::Point click_location(screen_location); |
| + aura::Window* target = GetTargetWindow(); |
| + ::wm::ConvertPointFromScreen(target, &click_location); |
| + aura::WindowTreeHost* host = target->GetHost(); |
| + host->ConvertPointToHost(&click_location); |
| + tap_down_location_ = click_location; |
| + SetTapDownTarget(target); |
| +} |
| + |
| +void LongPressAutoclickRingHandler::StartAnimation(int delay_ms) { |
| + switch (current_animation_type_) { |
| + case GROW_ANIMATION: { |
| + aura::Window* root_window = tap_down_target_->GetRootWindow(); |
| + if (!root_window) { |
| + StopAutoclickRing(); |
| + return; |
| + } |
| + view_.reset( |
| + new LongPressAutoclickRingView(tap_down_location_, root_window)); |
| + SetDuration(delay_ms); |
| + Start(); |
| + break; |
| + } |
| + case SHRINK_ANIMATION: |
| + SetDuration(delay_ms); |
| + Start(); |
| + break; |
| + default: |
| + NOTREACHED(); |
| + break; |
| + } |
| +} |
| + |
| +void LongPressAutoclickRingHandler::StopAutoclickRing() { |
| + // Since, Animation::Stop() calls AnimationStopped(), we need to reset the |
| + // |current_animation_type_| before Stop(), otherwise AnimationStopped() may |
| + // start the timer again. |
| + current_animation_type_ = NONE; |
| + Stop(); |
| + view_.reset(); |
| + SetTapDownTarget(NULL); |
| +} |
| + |
| +void LongPressAutoclickRingHandler::SetTapDownTarget(aura::Window* target) { |
| + if (tap_down_target_ == target) |
| + return; |
| + |
| + if (tap_down_target_) |
| + tap_down_target_->RemoveObserver(this); |
| + tap_down_target_ = target; |
| + if (tap_down_target_) |
| + tap_down_target_->AddObserver(this); |
| +} |
| + |
| +void LongPressAutoclickRingHandler::AnimateToState(double state) { |
| + DCHECK(view_.get()); |
| + switch (current_animation_type_) { |
| + case GROW_ANIMATION: |
| + view_->SetNewLocation(GetLastMouseLocation(), GetTargetWindow()); |
| + view_->UpdateWithGrowAnimation(this); |
| + break; |
| + case SHRINK_ANIMATION: |
| + view_->SetNewLocation(GetLastMouseLocation(), GetTargetWindow()); |
| + view_->UpdateWithShrinkAnimation(this); |
| + break; |
| + default: |
| + NOTREACHED(); |
| + break; |
| + } |
| +} |
| + |
| +void LongPressAutoclickRingHandler::AnimationStopped() { |
| + switch (current_animation_type_) { |
| + case GROW_ANIMATION: |
| + current_animation_type_ = SHRINK_ANIMATION; |
| + StartAnimation(0); |
| + break; |
| + case SHRINK_ANIMATION: |
| + current_animation_type_ = NONE; |
| + // fall through to reset the view. |
| + default: |
| + view_.reset(); |
| + SetTapDownTarget(NULL); |
| + break; |
| + } |
| +} |
| + |
| +void LongPressAutoclickRingHandler::OnWindowDestroying(aura::Window* window) { |
| + DCHECK_EQ(tap_down_target_, window); |
| + StopAutoclickRing(); |
| +} |
| + |
| +} // namespace ash |