Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1061)

Unified Diff: ui/views/radial_menu/radial_menu_views.cc

Issue 10823025: Adding new maximize menu according to spec (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Found some edge cases for menu destruction Created 8 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698