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 |