| Index: ui/views/radial_menu/radial_menu_views.cc
|
| diff --git a/ui/views/radial_menu/radial_menu_views.cc b/ui/views/radial_menu/radial_menu_views.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..11e04e53b10b20ca3ffcd8a81372fac5d8e4bedd
|
| --- /dev/null
|
| +++ b/ui/views/radial_menu/radial_menu_views.cc
|
| @@ -0,0 +1,877 @@
|
| +// Copyright (c) 2012 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 "ui/views/radial_menu/radial_menu_views.h"
|
| +
|
| +#include "base/time.h"
|
| +#include "base/timer.h"
|
| +#include "third_party/skia/include/core/SkCanvas.h"
|
| +#include "third_party/skia/include/core/SkPaint.h"
|
| +#include "third_party/skia/include/core/SkShader.h"
|
| +#include "third_party/skia/include/core/SkXfermode.h"
|
| +#include "third_party/skia/include/effects/SkGradientShader.h"
|
| +#include "ui/base/animation/linear_animation.h"
|
| +#include "ui/gfx/rect.h"
|
| +#include "ui/gfx/screen.h"
|
| +#include "ui/gfx/image/image_skia.h"
|
| +#include "ui/views/controls/image_view.h"
|
| +#include "ui/views/widget/widget.h"
|
| +
|
| +namespace views {
|
| +
|
| +namespace {
|
| +
|
| +// The animation framerate.
|
| +static const int kFrameRateHz = 60;
|
| +
|
| +// The opacity values for menu items dependent on their state.
|
| +static const float kHiddenOpacity = 0.0;
|
| +static const float kNormalOpacity = 0.8;
|
| +static const float kDisabledOpacity = 0.4;
|
| +static const float kActiveOpacity = 1.0;
|
| +
|
| +// The size values for menu items dependent on their state.
|
| +static const float kHiddenSize = 0.0;
|
| +static const float kNormalSize = 1.0;
|
| +static const float kActiveSize = 1.1;
|
| +
|
| +// The default stroke width around the wedges.
|
| +static const int kStrokeWidth = 2;
|
| +
|
| +static SkColor kUpperGradient = 0xffe0e0e0;
|
| +static SkColor kLowerGradient = 0xff808080;
|
| +static SkColor kStrokeColor = 0xffffffff;
|
| +static SkColor kTransparentBlack = 0x00000000;
|
| +
|
| +// How long to spend moving downwards and fading out after waiting.
|
| +// (In this case 5 frames will be used for the animation).
|
| +static const double kDefaultTransitionLengthMS = 5 * 1.0 / kFrameRateHz;
|
| +static const double kDestructionTransitionLengthMS = 2 * 1.0 / kFrameRateHz;
|
| +// Within 3 frames we want to change the offset.
|
| +static const double kOffsetTransitionLengthMS = 3.0 / kFrameRateHz;
|
| +
|
| +
|
| +// A class to animate a parameter over time - allowing overlapping changes.
|
| +// The animation method is always linear.
|
| +class ValueAnimator {
|
| + public:
|
| + ValueAnimator(double initial_value, double current_time)
|
| + : start_time_(current_time),
|
| + current_time_(current_time),
|
| + end_time_(current_time),
|
| + start_value_(initial_value),
|
| + current_value_(initial_value),
|
| + end_value_(initial_value) { }
|
| +
|
| + // Initiate an animation of the parameter - note that it will smoothly
|
| + // change the value from its current state. |length| is the duration of
|
| + // the animation in seconds beginning from now + |delay| (in seconds).
|
| + // The |target_value| is the value which needs to be reached.
|
| + // Note: animation requests without a real change get ignored.
|
| + void SetAnimation(double length, double delay, double target_value);
|
| +
|
| + // Get the current value of the animator.
|
| + double GetCurrentValue() { return current_value_; }
|
| +
|
| + // Advance to the new time - should get called before GetCurrentValue
|
| + // gets called.
|
| + bool AdvanceTime(double current_time);
|
| +
|
| + private:
|
| + // The start, current and end time of the animation.
|
| + double start_time_;
|
| + double current_time_;
|
| + double end_time_;
|
| + // The start, current and end value of the animation.
|
| + double start_value_;
|
| + double current_value_;
|
| + double end_value_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(ValueAnimator);
|
| +};
|
| +
|
| +void ValueAnimator::SetAnimation(
|
| + double length,
|
| + double delay,
|
| + double target_value) {
|
| + // Get the current value since we might be in the middle of an animation.
|
| + start_value_ = current_value_;
|
| + end_value_ = target_value;
|
| + start_time_ = current_time_ + delay;
|
| + end_time_ = current_time_ + delay + length;
|
| +}
|
| +
|
| +bool ValueAnimator::AdvanceTime(double current_time) {
|
| + current_time_ = current_time;
|
| +
|
| + if (current_time_ >= end_time_ && current_value_ == end_value_)
|
| + return false;
|
| +
|
| + double value = start_value_;
|
| + if (current_time >= end_time_) {
|
| + value = end_value_;
|
| + } else if (current_time > start_time_) {
|
| + value = start_value_ + (end_value_ - start_value_) *
|
| + (current_time_ - start_time_) / (end_time_ - start_time_);
|
| + }
|
| +
|
| + if (current_value_ == value)
|
| + return false;
|
| +
|
| + current_value_ = value;
|
| + return true;
|
| +}
|
| +
|
| +// The class which handles a menu item's drawing and animation.
|
| +class RadialMenuItemVisual : public views::ImageView {
|
| + public:
|
| + explicit RadialMenuItemVisual(RadialMenuItem* info,
|
| + double current_time,
|
| + gfx::Rect bounds,
|
| + int inner_radius,
|
| + int outer_radius,
|
| + int stroke_width,
|
| + double center_degree,
|
| + double span_degrees);
|
| + ~RadialMenuItemVisual();
|
| +
|
| + // Change the state of the item accordingly.
|
| + void ShowMenuItem();
|
| + void SetMenuItemActive();
|
| + void HideMenuItem();
|
| +
|
| + // Update the time. The function will return true if the menu item is
|
| + // visible.
|
| + bool AdvanceTime(double current_time);
|
| +
|
| + // Returns true if the segment is a separator.
|
| + bool IsSeparator();
|
| +
|
| + // Returns the orientation of the wedge.
|
| + double center_degree() { return center_degree_ ;}
|
| +
|
| + // Return the type of menu.
|
| + RadialMenuItem::RadialMenuItemType type() {
|
| + return presentation_info_->Type();
|
| + }
|
| +
|
| + // The offset of the wedge to the designated place.
|
| + void SetSliderOffset(const gfx::Point& offset);
|
| +
|
| + // Returns the current fractional size of this element.
|
| + double RelativeSize();
|
| +
|
| + // Get - set the last known slider position.
|
| + void GetLastSliderValue(int& count, int& distance) {
|
| + count = last_slider_count_;
|
| + distance = last_slider_distance_;
|
| + }
|
| + void SetLastSliderValue(int count, int distance) {
|
| + last_slider_count_ = count;
|
| + last_slider_distance_ = distance;
|
| + }
|
| +
|
| + // Get the associated window of this item.
|
| + aura::Window* GetWindow() {
|
| + if (popup_)
|
| + return popup_->GetNativeWindow();
|
| + return NULL;
|
| + }
|
| +
|
| + private:
|
| + // Draw the wedge which is underneath the menu.
|
| + void CreateWedgeImage();
|
| +
|
| + void GetIconBounds(
|
| + SkRect& bounds, int xMid, int yMid, double radians);
|
| +
|
| + // The bounds on the screen for the menu.
|
| + gfx::Rect bounds_;
|
| +
|
| + // The widget for this menu element. If the widget is NULL it will is an
|
| + // empty space in the menu.
|
| + views::Widget* popup_;
|
| +
|
| + // The passed presentation information from the creator.
|
| + RadialMenuItem* presentation_info_;
|
| +
|
| + // Animated parameters over time. Note: They might change the animation
|
| + // while animating.
|
| + scoped_ptr<ValueAnimator> opacity_;
|
| + scoped_ptr<ValueAnimator> size_;
|
| +
|
| + scoped_ptr<ValueAnimator> slider_offset_x_;
|
| + scoped_ptr<ValueAnimator> slider_offset_y_;
|
| +
|
| + // Our bitmap which contains the initial wedge image.
|
| + SkBitmap* wedge_image_;
|
| +
|
| + // The graphical constants to draw the wedge.
|
| + int inner_radius_;
|
| + int outer_radius_;
|
| + int stroke_width_;
|
| + double center_degree_;
|
| + double span_degrees_;
|
| + // To avoid value / rounding instabilities we ignore changes in border areas
|
| + // by remembering the previous return value.
|
| + int last_slider_count_;
|
| + int last_slider_distance_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(RadialMenuItemVisual);
|
| +};
|
| +
|
| +RadialMenuItemVisual::RadialMenuItemVisual(
|
| + RadialMenuItem* item_info,
|
| + double current_time,
|
| + gfx::Rect bounds,
|
| + int inner_radius,
|
| + int outer_radius,
|
| + int stroke_width,
|
| + double center_degree,
|
| + double span_degrees)
|
| + : bounds_(bounds),
|
| + popup_(NULL),
|
| + presentation_info_(item_info),
|
| + opacity_(NULL),
|
| + size_(NULL),
|
| + slider_offset_x_(NULL),
|
| + slider_offset_y_(NULL),
|
| + wedge_image_(NULL),
|
| + inner_radius_(inner_radius),
|
| + outer_radius_(outer_radius),
|
| + stroke_width_(stroke_width),
|
| + center_degree_(center_degree),
|
| + span_degrees_(span_degrees),
|
| + last_slider_count_(0),
|
| + last_slider_distance_(0) {
|
| + // Elements might be empty to show nothing (separators).
|
| + if (presentation_info_->Type() == RadialMenuItem::RADIAL_BUTTON ||
|
| + presentation_info_->Type() == RadialMenuItem::RADIAL_SLIDER ||
|
| + presentation_info_->Type() == RadialMenuItem::RADIAL_DISABLED) {
|
| + CreateWedgeImage();
|
| + SetImage(gfx::ImageSkia(*wedge_image_));
|
| + popup_ = new views::Widget;
|
| + views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
|
| + params.transparent = true;
|
| + params.accept_events = false;
|
| + params.parent = NULL;
|
| + popup_->Init(params);
|
| + popup_->SetOpacity(0x00);
|
| + popup_->SetContentsView(this);
|
| + popup_->Show();
|
| +
|
| + // Set the animation up.
|
| + size_.reset(new ValueAnimator(0, current_time));
|
| + opacity_.reset(new ValueAnimator(kHiddenOpacity, current_time));
|
| + slider_offset_x_.reset(new ValueAnimator(0, current_time));
|
| + slider_offset_y_.reset(new ValueAnimator(0, current_time));
|
| + ShowMenuItem();
|
| + }
|
| +}
|
| +
|
| +RadialMenuItemVisual::~RadialMenuItemVisual() {
|
| + delete presentation_info_;
|
| +
|
| + if (!popup_)
|
| + return;
|
| +
|
| + popup_->Close();
|
| + popup_ = NULL;
|
| +
|
| + delete wedge_image_;
|
| +}
|
| +
|
| +void RadialMenuItemVisual::ShowMenuItem() {
|
| + if (!popup_)
|
| + return;
|
| +
|
| + size_->SetAnimation(kDefaultTransitionLengthMS, 0.0, kNormalSize);
|
| + opacity_->SetAnimation(kDefaultTransitionLengthMS, 0.0,
|
| + presentation_info_->Type() == RadialMenuItem::RADIAL_DISABLED ?
|
| + kDisabledOpacity : kNormalOpacity);
|
| + // Set the offset back to the origin.
|
| + slider_offset_x_->SetAnimation(0, 0.0, 0);
|
| + slider_offset_y_->SetAnimation(0, 0.0, 0);
|
| +}
|
| +
|
| +void RadialMenuItemVisual::SetMenuItemActive() {
|
| + if (!popup_)
|
| + return;
|
| +
|
| + // Note: By not remembering that we were already activated the item,
|
| + // we will re-activate the item several times which will create a non
|
| + // linear smooth animation.
|
| + size_->SetAnimation(kDefaultTransitionLengthMS, 0.0, kActiveSize);
|
| + opacity_->SetAnimation(kDefaultTransitionLengthMS, 0.0,
|
| + presentation_info_->Type() == RadialMenuItem::RADIAL_DISABLED ?
|
| + kDisabledOpacity : kActiveOpacity);
|
| +}
|
| +
|
| +void RadialMenuItemVisual::HideMenuItem() {
|
| + if (!popup_)
|
| + return;
|
| +
|
| + size_->SetAnimation(kDestructionTransitionLengthMS, 0.0, kHiddenSize);
|
| + opacity_->SetAnimation(kDestructionTransitionLengthMS, 0.0, kHiddenOpacity);
|
| +}
|
| +
|
| +void RadialMenuItemVisual::SetSliderOffset(const gfx::Point& offset) {
|
| + slider_offset_x_->SetAnimation(kOffsetTransitionLengthMS, 0.0, offset.x());
|
| + slider_offset_y_->SetAnimation(kOffsetTransitionLengthMS, 0.0, offset.y());
|
| +}
|
| +
|
| +bool RadialMenuItemVisual::AdvanceTime(double current_time) {
|
| + if (!popup_)
|
| + return false;
|
| +
|
| + // Bring the items into view by growing them quickly.
|
| + int xMid = bounds_.x() + bounds_.width() / 2;
|
| + int yMid = bounds_.y() + bounds_.height() / 2;
|
| + int xSize = bounds_.width() / 2;
|
| + int ySize = bounds_.height() / 2;
|
| + bool is_visible = false;
|
| +
|
| + bool offset_x_changed = slider_offset_x_->AdvanceTime(current_time);
|
| + bool offset_y_changed = slider_offset_y_->AdvanceTime(current_time);
|
| + if (size_->AdvanceTime(current_time) ||
|
| + offset_x_changed || offset_y_changed) {
|
| + double current_size = size_->GetCurrentValue();
|
| + // Grow / shrink item from the center point.
|
| + popup_->SetBounds(gfx::Rect(
|
| + xMid - xSize * current_size + slider_offset_x_->GetCurrentValue(),
|
| + yMid - ySize * current_size + slider_offset_y_->GetCurrentValue(),
|
| + 2 * xSize * current_size, 2 * ySize * current_size));
|
| + // When we see at least one pixel it is visible.
|
| + is_visible = 2 * xSize * current_size > 1.0;
|
| + if (is_visible) {
|
| + // It is necessary to scale the image to the target size, otherwise
|
| + // it would only get cropped.
|
| + SetImageSize(
|
| + gfx::Size(2 * xSize * current_size, 2 * ySize * current_size));
|
| + }
|
| + }
|
| +
|
| + if (opacity_->AdvanceTime(current_time)) {
|
| + unsigned char opacity = static_cast<unsigned char>(
|
| + opacity_->GetCurrentValue() * 255.0);
|
| + popup_->SetOpacity(opacity);
|
| + }
|
| +
|
| + return is_visible;
|
| +}
|
| +
|
| +bool RadialMenuItemVisual::IsSeparator() {
|
| + return (popup_ == NULL ? true : false);
|
| +}
|
| +
|
| +double RadialMenuItemVisual::RelativeSize() {
|
| + return size_.get() ? size_->GetCurrentValue() : 1.0;
|
| +}
|
| +
|
| +void RadialMenuItemVisual::CreateWedgeImage() {
|
| + wedge_image_ = new SkBitmap();
|
| + wedge_image_->setConfig(SkBitmap::kARGB_8888_Config,
|
| + bounds_.width(),
|
| + bounds_.height());
|
| + wedge_image_->allocPixels();
|
| + SkCanvas canvas(*wedge_image_);
|
| + // Clear the bitmap fully transparent.
|
| + canvas.drawColor(kTransparentBlack);
|
| +
|
| + // Draw the wedge.
|
| + SkRect bounds;
|
| + // Note: xMid and yMid does not need to be the outer_radius if there is a
|
| + // smooth shadow involved below the wedges.
|
| + double radians = (center_degree_ + 90) * M_PI / 180.0;
|
| + int xMid = bounds_.width() / 2 + sin(radians) * 2 * stroke_width_;
|
| + int yMid = bounds_.height() / 2 - cos(radians) * 2 * stroke_width_;
|
| + bounds.set(xMid - outer_radius_, yMid - outer_radius_,
|
| + xMid + outer_radius_, yMid + outer_radius_);
|
| +
|
| + SkPaint paint;
|
| + paint.setColor(kStrokeColor);
|
| + paint.setAntiAlias(true);
|
| + paint.setStyle(SkPaint::kFill_Style);
|
| +
|
| + // A gradient background image, First point: top, second point: bottom.
|
| + const SkPoint pts[] = { { 0, 0 }, { 0, bounds_.height() } };
|
| + const SkColor colors[] = { kUpperGradient, kLowerGradient };
|
| + SkShader* shader = SkGradientShader::CreateLinear(
|
| + pts, colors, NULL, 2, SkShader::kClamp_TileMode);
|
| + paint.setShader(shader);
|
| + shader->unref();
|
| + canvas.drawArc(bounds,
|
| + center_degree_ - span_degrees_ / 2,
|
| + span_degrees_,
|
| + true,
|
| + paint);
|
| +
|
| + paint.setStyle(SkPaint::kStroke_Style);
|
| + paint.setStrokeWidth(stroke_width_);
|
| + paint.setShader(NULL);
|
| + canvas.drawArc(bounds,
|
| + center_degree_ - span_degrees_ / 2,
|
| + span_degrees_,
|
| + true,
|
| + paint);
|
| +
|
| + // Erase the inner portion of the wedge again.
|
| + if (inner_radius_) {
|
| + // Erase inner wedge - need to use clear mode to get rid of alpha.
|
| + paint.setXfermodeMode(SkXfermode::kClear_Mode);
|
| + paint.setStyle(SkPaint::kStrokeAndFill_Style);
|
| + paint.setColor(kTransparentBlack);
|
| + paint.setAntiAlias(false);
|
| + // Adjust our target area (keep part of the stroke area).
|
| + // This is necessary to get the antialiased alpha pixels correct.
|
| + bounds.set(xMid - inner_radius_ + stroke_width_ / 2,
|
| + yMid - inner_radius_ + stroke_width_ / 2,
|
| + xMid + inner_radius_ - stroke_width_ / 2,
|
| + yMid + inner_radius_ - stroke_width_ / 2);
|
| + // Extend span range to get rid of anti aliased pixels.
|
| + canvas.drawArc(bounds,
|
| + center_degree_ - span_degrees_ / 2 - 5,
|
| + span_degrees_ + 10,
|
| + true,
|
| + paint);
|
| +
|
| + // To eliminate antialiased pixels in center area, we draw a tiny circle.
|
| + int clear_radius = 2 * stroke_width_;
|
| + if (inner_radius_ < clear_radius)
|
| + clear_radius = inner_radius_;
|
| + bounds.set(xMid - clear_radius, yMid - clear_radius,
|
| + xMid + clear_radius, yMid + clear_radius);
|
| + paint.setStyle(SkPaint::kFill_Style);
|
| + canvas.drawArc(bounds, 0, 360, true, paint);
|
| +
|
| + // Draw the inner stroke to fill the outline.
|
| + paint.setStyle(SkPaint::kStroke_Style);
|
| + paint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
|
| + paint.setColor(kStrokeColor);
|
| + paint.setAntiAlias(true);
|
| + bounds.set(xMid - inner_radius_, yMid - inner_radius_,
|
| + xMid + inner_radius_, yMid + inner_radius_);
|
| + canvas.drawArc(bounds,
|
| + center_degree_ - span_degrees_ / 2,
|
| + span_degrees_,
|
| + false,
|
| + paint);
|
| + }
|
| + if (presentation_info_->Icon()) {
|
| + // Draw the action icon into the wedge.
|
| + SkPaint bitmap_paint;
|
| + bitmap_paint.setFilterBitmap(true);
|
| + bitmap_paint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
|
| + GetIconBounds(bounds, xMid, yMid, radians);
|
| + canvas.drawBitmapRect(
|
| + *(presentation_info_->Icon()->bitmap()), NULL, bounds, &bitmap_paint);
|
| + }
|
| +};
|
| +
|
| +void RadialMenuItemVisual::GetIconBounds(
|
| + SkRect& bounds, int xMid, int yMid, double radians) {
|
| + // Get the mid point for the image for this wedge.
|
| + int xMidIcon = xMid + sin(radians) * (inner_radius_ + outer_radius_) / 2;
|
| + int yMidIcon = yMid - cos(radians) * (inner_radius_ + outer_radius_) / 2;
|
| +
|
| + // Determine the size of the image.
|
| + // ___
|
| + // Assume we are looking at the wedge as it stands up like this: \_/
|
| + // There are two size limiting factors:
|
| + // 1. The height as given by the inner and outer radius.
|
| + int size_radius = outer_radius_ - inner_radius_;
|
| + // 2. The width as given by the wedge "degree span". For simplicity we ignore
|
| + // the fact that the wedge will be wider at the top then at the bottom.
|
| + double half_width = sin(span_degrees_ / 2 * M_PI / 180.0);
|
| + int size_midpoint = static_cast<int>(half_width * 2 *
|
| + (outer_radius_ + inner_radius_) / 2);
|
| + // Taking the minimum size we have found.
|
| + int size = ((size_midpoint < size_radius) ? size_midpoint : size_radius);
|
| + // Shrink this to three fourths to compensate for the shape and give spacing.
|
| + size = (3 * size) / 4;
|
| + int native_width = presentation_info_->Icon()->width();
|
| + int native_height = presentation_info_->Icon()->height();
|
| + double aspect = static_cast<double>(native_width) /
|
| + static_cast<double>(native_height);
|
| + // Finally we do not want to grow the image bigger then 2x its actual size.
|
| + if (size > 2 * native_width)
|
| + size = 2 * native_width;
|
| + if (size > 2 * native_height)
|
| + size = 2 * native_height;
|
| + // Calculate the size to use taking the size as the maximum dimension and
|
| + // using the aspect.
|
| + int height = size;
|
| + int width = size;
|
| + if (aspect >= 1.0)
|
| + height /= aspect;
|
| + else
|
| + width *= aspect;
|
| + bounds.set(xMidIcon - width / 2, yMidIcon - height / 2,
|
| + xMidIcon + width / 2, yMidIcon + height / 2);
|
| +}
|
| +
|
| +// The implementation of the radial menu.
|
| +class RadialMenuImpl : public RadialMenu {
|
| + public:
|
| + explicit RadialMenuImpl(const gfx::Point& location,
|
| + int outer_radius,
|
| + int inner_radius,
|
| + double rotation,
|
| + const RadialMenuItems items,
|
| + int pointer_activation_area_extension);
|
| +
|
| + // Set the item with |index| to be the element which is active
|
| + // (e.g. get hovered over).
|
| + void SetActiveItem(int index);
|
| +
|
| + // Radial Menu implementation.
|
| + virtual void CloseAndDelete(bool immediate_close) OVERRIDE;
|
| + virtual bool HitMenuItemTest(
|
| + const gfx::Point& location, int* item, int* count) OVERRIDE;
|
| + virtual aura::Window* GetWindow() OVERRIDE;
|
| + virtual void set_pointer_activation_area_extension(int expansion) OVERRIDE {
|
| + pointer_activation_area_extension_ = expansion;
|
| + }
|
| +
|
| + static RadialMenu* CreateRadialMenuInstance(
|
| + const gfx::Point& location,
|
| + int outer_radius,
|
| + int inner_radius,
|
| + double rotation,
|
| + const RadialMenuItems items) OVERRIDE;
|
| + static RadialMenuItem* CreateRadialMenuItemInstance(
|
| + RadialMenuItem::RadialMenuItemType type,
|
| + gfx::ImageSkia* icon,
|
| + bool delete_icon) OVERRIDE;
|
| +
|
| + private:
|
| + ~RadialMenuImpl();
|
| +
|
| + // Updates the menu over time to the correct state.
|
| + // (e.g. show / hide / hover / ..)
|
| + void PeriodicAnimateMenu();
|
| +
|
| + // Update the current time relative to the creation time.
|
| + void UpdateCurrentTime();
|
| +
|
| + // This are all menu items. The item at index 0 is the top most and then
|
| + // they rotate clockwise from there.
|
| + std::vector<RadialMenuItemVisual*> items_;
|
| +
|
| + // The screen location of the entire menu.
|
| + gfx::Rect bounds_;
|
| +
|
| + // The outer and inner radius of the menu.
|
| + int outer_radius_;
|
| + int inner_radius_;
|
| + int stroke_width_;
|
| + // The whole menu got rotated by this many degrees.
|
| + int rotation_;
|
| +
|
| + // The timer which is used for our events.
|
| + base::RepeatingTimer<RadialMenuImpl> timer_;
|
| +
|
| + // The timer "interrupt" can be very imprecise. As such the correct time
|
| + // gets calculated on use.
|
| + double current_time_;
|
| + const base::TimeTicks start_time_;
|
| +
|
| + // When the menu gets gradually removed from the screen this is true.
|
| + bool delete_in_progress_;
|
| + // When the menu deletion is performed this is true. This is put here to
|
| + // avoid asynchronous animation calls coming in.
|
| + bool deleting_now_;
|
| +
|
| + // If true the pointer needs to go exactly over the button to activate,
|
| + // otherwise anything over or behind the button is fine.
|
| + int pointer_activation_area_extension_;
|
| +
|
| + // The active item. A negative value means that none is active.
|
| + int active_item_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(RadialMenuImpl);
|
| +};
|
| +
|
| +RadialMenuImpl::RadialMenuImpl(const gfx::Point& location,
|
| + int outer_radius,
|
| + int inner_radius,
|
| + double rotation,
|
| + const RadialMenuItems items,
|
| + int pointer_activation_area_extension)
|
| + : outer_radius_(outer_radius),
|
| + inner_radius_(inner_radius),
|
| + stroke_width_(kStrokeWidth),
|
| + rotation_(rotation),
|
| + current_time_(0.0),
|
| + start_time_(base::TimeTicks::Now()),
|
| + delete_in_progress_(false),
|
| + deleting_now_(false),
|
| + pointer_activation_area_extension_(pointer_activation_area_extension),
|
| + active_item_(-1) {
|
| + // We use 3 times the stroke width for: 1x for the stroke itself and 2x
|
| + // to offset the wedges of each other (so that they don't touch).
|
| + bounds_ = gfx::Rect(location.x() - outer_radius_ - 3 * stroke_width_,
|
| + location.y() - outer_radius_ - 3 * stroke_width_,
|
| + 2 * (outer_radius_ + 3 * stroke_width_),
|
| + 2 * (outer_radius_ + 3 * stroke_width_));
|
| + // Each item spans this amount of degrees:
|
| + double circular_span = 360.0 / items.size();
|
| + // We rotate the menu negative 90 degrees so that the first item is at the
|
| + // top.
|
| + rotation -= 90;
|
| + // Populate the menu with items.
|
| + for (size_t i = 0; i < items.size(); i++) {
|
| + // Update the time since considerable time could have already passed.
|
| + UpdateCurrentTime();
|
| + // Note: The -90 is to rotate the first segment to the top.
|
| + items_.push_back(new RadialMenuItemVisual(items[i],
|
| + current_time_,
|
| + bounds_,
|
| + inner_radius_,
|
| + outer_radius_,
|
| + stroke_width_,
|
| + i * circular_span + rotation,
|
| + circular_span));
|
| + }
|
| +
|
| + // Ping the animation code once to initialize.
|
| + PeriodicAnimateMenu();
|
| +
|
| + // Kick off our periodic timer to animate the menu.
|
| + timer_.Start(FROM_HERE,
|
| + base::TimeDelta::FromMilliseconds(1000 / kFrameRateHz),
|
| + this,
|
| + &RadialMenuImpl::PeriodicAnimateMenu);
|
| +}
|
| +
|
| +void RadialMenuImpl::SetActiveItem(int index) {
|
| + DCHECK(index < static_cast<unsigned char>(items_.size()));
|
| + if (delete_in_progress_ || index == active_item_)
|
| + return;
|
| +
|
| + if (active_item_ >= 0)
|
| + items_[active_item_]->ShowMenuItem();
|
| +
|
| + active_item_ = index;
|
| +
|
| + if (active_item_ >= 0)
|
| + items_[active_item_]->SetMenuItemActive();
|
| +}
|
| +
|
| +void RadialMenuImpl::CloseAndDelete(bool immediate_close) {
|
| + if (delete_in_progress_)
|
| + return;
|
| +
|
| + delete_in_progress_ = true;
|
| +
|
| + if (immediate_close) {
|
| + delete this;
|
| + } else {
|
| + // Fade away all segments of the menu. Once they are gone the menu will
|
| + // destroy itself.
|
| + for (size_t i = 0; i < items_.size(); i++)
|
| + items_[i]->HideMenuItem();
|
| + }
|
| +}
|
| +
|
| +bool RadialMenuImpl::HitMenuItemTest(
|
| + const gfx::Point& location, int* returned_item, int* returned_count) {
|
| + if (delete_in_progress_)
|
| + return false;
|
| +
|
| + // Get the mid point.
|
| + double x = bounds_.x() + bounds_.width() / 2;
|
| + double y = bounds_.y() + bounds_.height() / 2;
|
| + // The distance of the coordinates.
|
| + double dx = x - location.x();
|
| + double dy = y - location.y();
|
| +
|
| + double distance = sqrt(dx * dx + dy * dy);
|
| + if (distance < inner_radius_) {
|
| + SetActiveItem(-1);
|
| + return false;
|
| + }
|
| + int segments = items_.size();
|
| + // Get the rotation of the point around the mid point.
|
| + double degree = atan2(dy, dx) * 180 / M_PI;
|
| + // The segments are offsetted by three rotations:
|
| + // - 90 degree rotation to get the first element to the top.
|
| + // - half a segment width to get to the start of the segment
|
| + // + the callers rotation
|
| + double first_start_offset = 90 - 360 / segments / 2 + rotation_;
|
| + // Offset the retrieved rotation and limit it to positive degrees [0..360].
|
| + int adjusted_degree =
|
| + static_cast<int>(3600 + degree - first_start_offset) % 360;
|
| + int item = adjusted_degree / (360 / segments);
|
| + // For normal buttons we want to only activate it when over the button.
|
| + // For sliders we want to have the width a bit wider (thus two times the
|
| + // with of a wedge circle).
|
| + int step_width = (items_[item]->type() == RadialMenuItem::RADIAL_SLIDER) ?
|
| + (2 * (outer_radius_ - inner_radius_)) : (outer_radius_ - inner_radius_);
|
| + // We need to adjust the size relative to the size of the wedge.
|
| + distance /= items_[item]->RelativeSize();
|
| + int count = ceil((distance - inner_radius_) / step_width);
|
| + if (items_[item]->type() == RadialMenuItem::RADIAL_SLIDER) {
|
| + // TODO(skuhne): Sliders are currently expected to be left / right only.
|
| + // This can be changed by calculating the distance projected onto the moving
|
| + // axis.
|
| + DCHECK((static_cast<int>(items_[item]->center_degree()) + 360) % 180 == 90);
|
| + if (distance < outer_radius_)
|
| + items_[item]->SetSliderOffset(gfx::Point(0, 0));
|
| + else
|
| + items_[item]->SetSliderOffset(
|
| + gfx::Point(-(dx + (dx < 0 ? outer_radius_ : -outer_radius_)), 0));
|
| + }
|
| + if (items_[item]->IsSeparator() ||
|
| + items_[item]->type() == RadialMenuItem::RADIAL_DISABLED ||
|
| + (count > pointer_activation_area_extension_ &&
|
| + items_[item]->type() != RadialMenuItem::RADIAL_SLIDER)) {
|
| + SetActiveItem(-1);
|
| + return false;
|
| + }
|
| + SetActiveItem(item);
|
| + // Transfer the found return information.
|
| + *returned_item = item;
|
| + if (items_[item]->type() == RadialMenuItem::RADIAL_SLIDER) {
|
| + int last_distance = 0;
|
| + int last_count = 0;
|
| + items_[item]->GetLastSliderValue(last_count, last_distance);
|
| + if (last_count != count) {
|
| + // To avoid value jittering we only accept changes with more then half
|
| + // step delta.
|
| + if (abs(last_distance - distance) < step_width / 2 && count)
|
| + count = last_count;
|
| + else
|
| + items_[item]->SetLastSliderValue(count, distance);
|
| + }
|
| + *returned_count = count;
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +RadialMenuImpl::~RadialMenuImpl() {
|
| + timer_.Stop();
|
| + // Delete all menu item related resources.
|
| + while (items_.size()) {
|
| + RadialMenuItemVisual* item = items_.back();
|
| + items_.pop_back();
|
| + delete item;
|
| + }
|
| +}
|
| +
|
| +void RadialMenuImpl::PeriodicAnimateMenu() {
|
| + if (deleting_now_)
|
| + return;
|
| +
|
| + // Since the interrupt is not really happening in "the accurate time", the
|
| + // time needs to get calculated here.
|
| + UpdateCurrentTime();
|
| +
|
| + // We need to check if the menu is still at least partially visible.
|
| + // This is required for self destruction when we were asked to shut down.
|
| + bool is_visible = false;
|
| +
|
| + for (size_t i = 0; i < items_.size(); i++)
|
| + is_visible |= items_[i]->AdvanceTime(current_time_);
|
| +
|
| + if (delete_in_progress_ && !is_visible) {
|
| + deleting_now_ = true;
|
| + delete this;
|
| + }
|
| +}
|
| +
|
| +void RadialMenuImpl::UpdateCurrentTime() {
|
| + current_time_ = (base::TimeTicks::Now() - start_time_).InSecondsF();
|
| +}
|
| +
|
| +aura::Window* RadialMenuImpl::GetWindow() {
|
| + for (size_t i = 0; i < items_.size(); i++) {
|
| + if (items_[i]->GetWindow())
|
| + return items_[i]->GetWindow();
|
| + }
|
| + return NULL;
|
| +}
|
| +
|
| +// The class which represents the menu item structure which assembles the menu.
|
| +class RadialMenuItemImpl: public RadialMenuItem {
|
| + public:
|
| + RadialMenuItemImpl(
|
| + RadialMenuItemType type, gfx::ImageSkia* icon, bool delete_icon);
|
| +
|
| + virtual RadialMenuItemType Type() OVERRIDE { return type_; }
|
| + virtual gfx::ImageSkia* Icon() OVERRIDE { return icon_; }
|
| + virtual bool DeleteIcon() OVERRIDE {return delete_icon_; }
|
| +
|
| + virtual ~RadialMenuItemImpl();
|
| +
|
| + private:
|
| + // The type of the menu.
|
| + RadialMenuItemType type_;
|
| + // The image to use for this item.
|
| + gfx::ImageSkia* icon_;
|
| + // If true, the icon should get deleted on destruction.
|
| + bool delete_icon_;
|
| +};
|
| +
|
| +RadialMenuItemImpl::RadialMenuItemImpl(
|
| + RadialMenuItemType type, gfx::ImageSkia* icon, bool delete_icon)
|
| + : type_(type),
|
| + icon_(icon),
|
| + delete_icon_(delete_icon) {}
|
| +
|
| +RadialMenuItemImpl::~RadialMenuItemImpl() {
|
| + if (delete_icon_ && icon_)
|
| + delete icon_;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +// static
|
| +RadialMenu* RadialMenu::CreateRadialMenuInstance(
|
| + const gfx::Point& location,
|
| + int outer_radius,
|
| + int inner_radius,
|
| + double rotation,
|
| + const RadialMenuItems items,
|
| + int pointer_activation_area_extension,
|
| + bool clip_menu_to_screen) {
|
| + DCHECK(pointer_activation_area_extension >= 1);
|
| + int min_radius = outer_radius;
|
| + if (clip_menu_to_screen) {
|
| + // Check that the menu stays within the visible screen by resizing if
|
| + // necessary.
|
| + gfx::Rect screen = gfx::Screen::GetDisplayNearestPoint(location).bounds();
|
| + if (location.x() - screen.x() < min_radius)
|
| + min_radius = location.x() - screen.x();
|
| + if (location.y() - screen.y() < min_radius)
|
| + min_radius = location.y() - screen.y();
|
| + if (screen.right() - location.x() < min_radius)
|
| + min_radius = screen.right() - location.x();
|
| + if (screen.bottom() - location.y() < min_radius)
|
| + min_radius = screen.bottom() - location.y();
|
| + if (min_radius < 48)
|
| + min_radius = 48;
|
| + inner_radius = (inner_radius * min_radius) / outer_radius;
|
| + }
|
| +
|
| + // The animation will delete itself when it's finished or when the tab
|
| + // contents is hidden or destroyed.
|
| + return new RadialMenuImpl(
|
| + location,
|
| + min_radius,
|
| + inner_radius,
|
| + rotation,
|
| + items,
|
| + pointer_activation_area_extension);
|
| +}
|
| +
|
| +// static
|
| +RadialMenuItem* RadialMenuItem::CreateRadialMenuItemInstance(
|
| + RadialMenuItem::RadialMenuItemType type,
|
| + gfx::ImageSkia* icon,
|
| + bool delete_icon) {
|
| + return new RadialMenuItemImpl(type, icon, delete_icon);
|
| +}
|
| +
|
| +} // namespace views
|
|
|