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

Side by Side 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: Fixed merging issues Created 8 years, 4 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/views/radial_menu/radial_menu_views.h"
6
7 #include "base/time.h"
8 #include "base/timer.h"
9 #include "third_party/skia/include/core/SkCanvas.h"
10 #include "third_party/skia/include/core/SkPaint.h"
11 #include "third_party/skia/include/core/SkShader.h"
12 #include "third_party/skia/include/core/SkXfermode.h"
13 #include "third_party/skia/include/effects/SkGradientShader.h"
14 #include "ui/base/animation/linear_animation.h"
15 #include "ui/gfx/rect.h"
16 #include "ui/gfx/screen.h"
17 #include "ui/gfx/image/image_skia.h"
18 #include "ui/views/controls/image_view.h"
19 #include "ui/views/widget/widget.h"
20
21 namespace views {
22
23 namespace {
24
25 // The animation framerate.
26 static const int kFrameRateHz = 60;
27
28 // The opacity values for menu items dependent on their state.
29 static const float kHiddenOpacity = 0.0;
30 static const float kNormalOpacity = 0.8;
31 static const float kDisabledOpacity = 0.4;
32 static const float kActiveOpacity = 1.0;
33
34 // The size values for menu items dependent on their state.
35 static const float kHiddenSize = 0.0;
36 static const float kNormalSize = 1.0;
37 static const float kActiveSize = 1.1;
38
39 // The default stroke width around the wedges.
40 static const int kStrokeWidth = 2;
41
42 static SkColor kUpperGradient = 0xffe0e0e0;
43 static SkColor kLowerGradient = 0xff808080;
44 static SkColor kStrokeColor = 0xffffffff;
45 static SkColor kTransparentBlack = 0x00000000;
46
47 // How long to spend moving downwards and fading out after waiting.
48 // (In this case 5 frames will be used for the animation).
49 static const double kDefaultTransitionLengthMS = 5 * 1.0 / kFrameRateHz;
50 static const double kDestructionTransitionLengthMS = 2 * 1.0 / kFrameRateHz;
51 // Within 3 frames we want to change the offset.
52 static const double kOffsetTransitionLengthMS = 3.0 / kFrameRateHz;
53
54
55 // A class to animate a parameter over time - allowing overlapping changes.
56 // The animation method is always linear.
57 class ValueAnimator {
58 public:
59 ValueAnimator(double initial_value, double current_time)
60 : start_time_(current_time),
61 current_time_(current_time),
62 end_time_(current_time),
63 start_value_(initial_value),
64 current_value_(initial_value),
65 end_value_(initial_value) { }
66
67 // Initiate an animation of the parameter - note that it will smoothly
68 // change the value from its current state. |length| is the duration of
69 // the animation in seconds beginning from now + |delay| (in seconds).
70 // The |target_value| is the value which needs to be reached.
71 // Note: animation requests without a real change get ignored.
72 void SetAnimation(double length, double delay, double target_value);
73
74 // Get the current value of the animator.
75 double GetCurrentValue() { return current_value_; }
76
77 // Advance to the new time - should get called before GetCurrentValue
78 // gets called.
79 bool AdvanceTime(double current_time);
80
81 private:
82 // The start, current and end time of the animation.
83 double start_time_;
84 double current_time_;
85 double end_time_;
86 // The start, current and end value of the animation.
87 double start_value_;
88 double current_value_;
89 double end_value_;
90
91 DISALLOW_COPY_AND_ASSIGN(ValueAnimator);
92 };
93
94 void ValueAnimator::SetAnimation(
95 double length,
96 double delay,
97 double target_value) {
98 // Get the current value since we might be in the middle of an animation.
99 start_value_ = current_value_;
100 end_value_ = target_value;
101 start_time_ = current_time_ + delay;
102 end_time_ = current_time_ + delay + length;
103 }
104
105 bool ValueAnimator::AdvanceTime(double current_time) {
106 current_time_ = current_time;
107
108 if (current_time_ >= end_time_ && current_value_ == end_value_)
109 return false;
110
111 double value = start_value_;
112 if (current_time >= end_time_) {
113 value = end_value_;
114 } else if (current_time > start_time_) {
115 value = start_value_ + (end_value_ - start_value_) *
116 (current_time_ - start_time_) / (end_time_ - start_time_);
117 }
118
119 if (current_value_ == value)
120 return false;
121
122 current_value_ = value;
123 return true;
124 }
125
126 // The class which handles a menu item's drawing and animation.
127 class RadialMenuItemVisual : public views::ImageView {
128 public:
129 explicit RadialMenuItemVisual(RadialMenuItem* info,
130 double current_time,
131 gfx::Rect bounds,
132 int inner_radius,
133 int outer_radius,
134 int stroke_width,
135 double center_degree,
136 double span_degrees);
137 ~RadialMenuItemVisual();
138
139 // Change the state of the item accordingly.
140 void ShowMenuItem();
141 void SetMenuItemActive();
142 void HideMenuItem();
143
144 // Update the time. The function will return true if the menu item is
145 // visible.
146 bool AdvanceTime(double current_time);
147
148 // Returns true if the segment is a separator.
149 bool IsSeparator();
150
151 // Returns the orientation of the wedge.
152 double center_degree() { return center_degree_ ;}
153
154 // Return the type of menu.
155 RadialMenuItem::RadialMenuItemType type() {
156 return presentation_info_->Type();
157 }
158
159 // The offset of the wedge to the designated place.
160 void SetSliderOffset(const gfx::Point& offset);
161
162 // Returns the current fractional size of this element.
163 double RelativeSize();
164
165 // Get - set the last known slider position.
166 void GetLastSliderValue(int& count, int& distance) {
167 count = last_slider_count_;
168 distance = last_slider_distance_;
169 }
170 void SetLastSliderValue(int count, int distance) {
171 last_slider_count_ = count;
172 last_slider_distance_ = distance;
173 }
174
175 // Get the associated window of this item.
176 aura::Window* GetWindow() {
177 if (popup_)
178 return popup_->GetNativeWindow();
179 return NULL;
180 }
181
182 private:
183 // Draw the wedge which is underneath the menu.
184 void CreateWedgeImage();
185
186 void GetIconBounds(
187 SkRect& bounds, int xMid, int yMid, double radians);
188
189 // The bounds on the screen for the menu.
190 gfx::Rect bounds_;
191
192 // The widget for this menu element. If the widget is NULL it will is an
193 // empty space in the menu.
194 views::Widget* popup_;
195
196 // The passed presentation information from the creator.
197 RadialMenuItem* presentation_info_;
198
199 // Animated parameters over time. Note: They might change the animation
200 // while animating.
201 scoped_ptr<ValueAnimator> opacity_;
202 scoped_ptr<ValueAnimator> size_;
203
204 scoped_ptr<ValueAnimator> slider_offset_x_;
205 scoped_ptr<ValueAnimator> slider_offset_y_;
206
207 // Our bitmap which contains the initial wedge image.
208 SkBitmap* wedge_image_;
209
210 // The graphical constants to draw the wedge.
211 int inner_radius_;
212 int outer_radius_;
213 int stroke_width_;
214 double center_degree_;
215 double span_degrees_;
216 // To avoid value / rounding instabilities we ignore changes in border areas
217 // by remembering the previous return value.
218 int last_slider_count_;
219 int last_slider_distance_;
220
221 DISALLOW_COPY_AND_ASSIGN(RadialMenuItemVisual);
222 };
223
224 RadialMenuItemVisual::RadialMenuItemVisual(
225 RadialMenuItem* item_info,
226 double current_time,
227 gfx::Rect bounds,
228 int inner_radius,
229 int outer_radius,
230 int stroke_width,
231 double center_degree,
232 double span_degrees)
233 : bounds_(bounds),
234 popup_(NULL),
235 presentation_info_(item_info),
236 opacity_(NULL),
237 size_(NULL),
238 slider_offset_x_(NULL),
239 slider_offset_y_(NULL),
240 wedge_image_(NULL),
241 inner_radius_(inner_radius),
242 outer_radius_(outer_radius),
243 stroke_width_(stroke_width),
244 center_degree_(center_degree),
245 span_degrees_(span_degrees),
246 last_slider_count_(0),
247 last_slider_distance_(0) {
248 // Elements might be empty to show nothing (separators).
249 if (presentation_info_->Type() == RadialMenuItem::RADIAL_BUTTON ||
250 presentation_info_->Type() == RadialMenuItem::RADIAL_SLIDER ||
251 presentation_info_->Type() == RadialMenuItem::RADIAL_DISABLED) {
252 CreateWedgeImage();
253 SetImage(gfx::ImageSkia(*wedge_image_));
254 popup_ = new views::Widget;
255 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
256 params.transparent = true;
257 params.accept_events = false;
258 params.parent = NULL;
259 popup_->Init(params);
260 popup_->SetOpacity(0x00);
261 popup_->SetContentsView(this);
262 popup_->Show();
263
264 // Set the animation up.
265 size_.reset(new ValueAnimator(0, current_time));
266 opacity_.reset(new ValueAnimator(kHiddenOpacity, current_time));
267 slider_offset_x_.reset(new ValueAnimator(0, current_time));
268 slider_offset_y_.reset(new ValueAnimator(0, current_time));
269 ShowMenuItem();
270 }
271 }
272
273 RadialMenuItemVisual::~RadialMenuItemVisual() {
274 delete presentation_info_;
275
276 if (!popup_)
277 return;
278
279 popup_->Close();
280 popup_ = NULL;
281
282 delete wedge_image_;
283 }
284
285 void RadialMenuItemVisual::ShowMenuItem() {
286 if (!popup_)
287 return;
288
289 size_->SetAnimation(kDefaultTransitionLengthMS, 0.0, kNormalSize);
290 opacity_->SetAnimation(kDefaultTransitionLengthMS, 0.0,
291 presentation_info_->Type() == RadialMenuItem::RADIAL_DISABLED ?
292 kDisabledOpacity : kNormalOpacity);
293 // Set the offset back to the origin.
294 slider_offset_x_->SetAnimation(0, 0.0, 0);
295 slider_offset_y_->SetAnimation(0, 0.0, 0);
296 }
297
298 void RadialMenuItemVisual::SetMenuItemActive() {
299 if (!popup_)
300 return;
301
302 // Note: By not remembering that we were already activated the item,
303 // we will re-activate the item several times which will create a non
304 // linear smooth animation.
305 size_->SetAnimation(kDefaultTransitionLengthMS, 0.0, kActiveSize);
306 opacity_->SetAnimation(kDefaultTransitionLengthMS, 0.0,
307 presentation_info_->Type() == RadialMenuItem::RADIAL_DISABLED ?
308 kDisabledOpacity : kActiveOpacity);
309 }
310
311 void RadialMenuItemVisual::HideMenuItem() {
312 if (!popup_)
313 return;
314
315 size_->SetAnimation(kDestructionTransitionLengthMS, 0.0, kHiddenSize);
316 opacity_->SetAnimation(kDestructionTransitionLengthMS, 0.0, kHiddenOpacity);
317 }
318
319 void RadialMenuItemVisual::SetSliderOffset(const gfx::Point& offset) {
320 slider_offset_x_->SetAnimation(kOffsetTransitionLengthMS, 0.0, offset.x());
321 slider_offset_y_->SetAnimation(kOffsetTransitionLengthMS, 0.0, offset.y());
322 }
323
324 bool RadialMenuItemVisual::AdvanceTime(double current_time) {
325 if (!popup_)
326 return false;
327
328 // Bring the items into view by growing them quickly.
329 int xMid = bounds_.x() + bounds_.width() / 2;
330 int yMid = bounds_.y() + bounds_.height() / 2;
331 int xSize = bounds_.width() / 2;
332 int ySize = bounds_.height() / 2;
333 bool is_visible = false;
334
335 bool offset_x_changed = slider_offset_x_->AdvanceTime(current_time);
336 bool offset_y_changed = slider_offset_y_->AdvanceTime(current_time);
337 if (size_->AdvanceTime(current_time) ||
338 offset_x_changed || offset_y_changed) {
339 double current_size = size_->GetCurrentValue();
340 // Grow / shrink item from the center point.
341 popup_->SetBounds(gfx::Rect(
342 xMid - xSize * current_size + slider_offset_x_->GetCurrentValue(),
343 yMid - ySize * current_size + slider_offset_y_->GetCurrentValue(),
344 2 * xSize * current_size, 2 * ySize * current_size));
345 // When we see at least one pixel it is visible.
346 is_visible = 2 * xSize * current_size > 1.0;
347 if (is_visible) {
348 // It is necessary to scale the image to the target size, otherwise
349 // it would only get cropped.
350 SetImageSize(
351 gfx::Size(2 * xSize * current_size, 2 * ySize * current_size));
352 }
353 }
354
355 if (opacity_->AdvanceTime(current_time)) {
356 unsigned char opacity = static_cast<unsigned char>(
357 opacity_->GetCurrentValue() * 255.0);
358 popup_->SetOpacity(opacity);
359 }
360
361 return is_visible;
362 }
363
364 bool RadialMenuItemVisual::IsSeparator() {
365 return (popup_ == NULL ? true : false);
366 }
367
368 double RadialMenuItemVisual::RelativeSize() {
369 return size_.get() ? size_->GetCurrentValue() : 1.0;
370 }
371
372 void RadialMenuItemVisual::CreateWedgeImage() {
373 wedge_image_ = new SkBitmap();
374 wedge_image_->setConfig(SkBitmap::kARGB_8888_Config,
375 bounds_.width(),
376 bounds_.height());
377 wedge_image_->allocPixels();
378 SkCanvas canvas(*wedge_image_);
379 // Clear the bitmap fully transparent.
380 canvas.drawColor(kTransparentBlack);
381
382 // Draw the wedge.
383 SkRect bounds;
384 // Note: xMid and yMid does not need to be the outer_radius if there is a
385 // smooth shadow involved below the wedges.
386 double radians = (center_degree_ + 90) * M_PI / 180.0;
387 int xMid = bounds_.width() / 2 + sin(radians) * 2 * stroke_width_;
388 int yMid = bounds_.height() / 2 - cos(radians) * 2 * stroke_width_;
389 bounds.set(xMid - outer_radius_, yMid - outer_radius_,
390 xMid + outer_radius_, yMid + outer_radius_);
391
392 SkPaint paint;
393 paint.setColor(kStrokeColor);
394 paint.setAntiAlias(true);
395 paint.setStyle(SkPaint::kFill_Style);
396
397 // A gradient background image, First point: top, second point: bottom.
398 const SkPoint pts[] = { { 0, 0 }, { 0, bounds_.height() } };
399 const SkColor colors[] = { kUpperGradient, kLowerGradient };
400 SkShader* shader = SkGradientShader::CreateLinear(
401 pts, colors, NULL, 2, SkShader::kClamp_TileMode);
402 paint.setShader(shader);
403 shader->unref();
404 canvas.drawArc(bounds,
405 center_degree_ - span_degrees_ / 2,
406 span_degrees_,
407 true,
408 paint);
409
410 paint.setStyle(SkPaint::kStroke_Style);
411 paint.setStrokeWidth(stroke_width_);
412 paint.setShader(NULL);
413 canvas.drawArc(bounds,
414 center_degree_ - span_degrees_ / 2,
415 span_degrees_,
416 true,
417 paint);
418
419 // Erase the inner portion of the wedge again.
420 if (inner_radius_) {
421 // Erase inner wedge - need to use clear mode to get rid of alpha.
422 paint.setXfermodeMode(SkXfermode::kClear_Mode);
423 paint.setStyle(SkPaint::kStrokeAndFill_Style);
424 paint.setColor(kTransparentBlack);
425 paint.setAntiAlias(false);
426 // Adjust our target area (keep part of the stroke area).
427 // This is necessary to get the antialiased alpha pixels correct.
428 bounds.set(xMid - inner_radius_ + stroke_width_ / 2,
429 yMid - inner_radius_ + stroke_width_ / 2,
430 xMid + inner_radius_ - stroke_width_ / 2,
431 yMid + inner_radius_ - stroke_width_ / 2);
432 // Extend span range to get rid of anti aliased pixels.
433 canvas.drawArc(bounds,
434 center_degree_ - span_degrees_ / 2 - 5,
435 span_degrees_ + 10,
436 true,
437 paint);
438
439 // To eliminate antialiased pixels in center area, we draw a tiny circle.
440 int clear_radius = 2 * stroke_width_;
441 if (inner_radius_ < clear_radius)
442 clear_radius = inner_radius_;
443 bounds.set(xMid - clear_radius, yMid - clear_radius,
444 xMid + clear_radius, yMid + clear_radius);
445 paint.setStyle(SkPaint::kFill_Style);
446 canvas.drawArc(bounds, 0, 360, true, paint);
447
448 // Draw the inner stroke to fill the outline.
449 paint.setStyle(SkPaint::kStroke_Style);
450 paint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
451 paint.setColor(kStrokeColor);
452 paint.setAntiAlias(true);
453 bounds.set(xMid - inner_radius_, yMid - inner_radius_,
454 xMid + inner_radius_, yMid + inner_radius_);
455 canvas.drawArc(bounds,
456 center_degree_ - span_degrees_ / 2,
457 span_degrees_,
458 false,
459 paint);
460 }
461 if (presentation_info_->Icon()) {
462 // Draw the action icon into the wedge.
463 SkPaint bitmap_paint;
464 bitmap_paint.setFilterBitmap(true);
465 bitmap_paint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
466 GetIconBounds(bounds, xMid, yMid, radians);
467 canvas.drawBitmapRect(
468 *(presentation_info_->Icon()->bitmap()), NULL, bounds, &bitmap_paint);
469 }
470 };
471
472 void RadialMenuItemVisual::GetIconBounds(
473 SkRect& bounds, int xMid, int yMid, double radians) {
474 // Get the mid point for the image for this wedge.
475 int xMidIcon = xMid + sin(radians) * (inner_radius_ + outer_radius_) / 2;
476 int yMidIcon = yMid - cos(radians) * (inner_radius_ + outer_radius_) / 2;
477
478 // Determine the size of the image.
479 // ___
480 // Assume we are looking at the wedge as it stands up like this: \_/
481 // There are two size limiting factors:
482 // 1. The height as given by the inner and outer radius.
483 int size_radius = outer_radius_ - inner_radius_;
484 // 2. The width as given by the wedge "degree span". For simplicity we ignore
485 // the fact that the wedge will be wider at the top then at the bottom.
486 double half_width = sin(span_degrees_ / 2 * M_PI / 180.0);
487 int size_midpoint = static_cast<int>(half_width * 2 *
488 (outer_radius_ + inner_radius_) / 2);
489 // Taking the minimum size we have found.
490 int size = ((size_midpoint < size_radius) ? size_midpoint : size_radius);
491 // Shrink this to three fourths to compensate for the shape and give spacing.
492 size = (3 * size) / 4;
493 int native_width = presentation_info_->Icon()->width();
494 int native_height = presentation_info_->Icon()->height();
495 double aspect = static_cast<double>(native_width) /
496 static_cast<double>(native_height);
497 // Finally we do not want to grow the image bigger then 2x its actual size.
498 if (size > 2 * native_width)
499 size = 2 * native_width;
500 if (size > 2 * native_height)
501 size = 2 * native_height;
502 // Calculate the size to use taking the size as the maximum dimension and
503 // using the aspect.
504 int height = size;
505 int width = size;
506 if (aspect >= 1.0)
507 height /= aspect;
508 else
509 width *= aspect;
510 bounds.set(xMidIcon - width / 2, yMidIcon - height / 2,
511 xMidIcon + width / 2, yMidIcon + height / 2);
512 }
513
514 // The implementation of the radial menu.
515 class RadialMenuImpl : public RadialMenu {
516 public:
517 explicit RadialMenuImpl(const gfx::Point& location,
518 int outer_radius,
519 int inner_radius,
520 double rotation,
521 const RadialMenuItems items,
522 int pointer_activation_area_extension);
523
524 // Set the item with |index| to be the element which is active
525 // (e.g. get hovered over).
526 void SetActiveItem(int index);
527
528 // Radial Menu implementation.
529 virtual void CloseAndDelete(bool immediate_close) OVERRIDE;
530 virtual bool HitMenuItemTest(
531 const gfx::Point& location, int* item, int* count) OVERRIDE;
532 virtual aura::Window* GetWindow() OVERRIDE;
533 virtual void set_pointer_activation_area_extension(int expansion) OVERRIDE {
534 pointer_activation_area_extension_ = expansion;
535 }
536
537 static RadialMenu* CreateRadialMenuInstance(
538 const gfx::Point& location,
539 int outer_radius,
540 int inner_radius,
541 double rotation,
542 const RadialMenuItems items) OVERRIDE;
543 static RadialMenuItem* CreateRadialMenuItemInstance(
544 RadialMenuItem::RadialMenuItemType type,
545 gfx::ImageSkia* icon,
546 bool delete_icon) OVERRIDE;
547
548 private:
549 ~RadialMenuImpl();
550
551 // Updates the menu over time to the correct state.
552 // (e.g. show / hide / hover / ..)
553 void PeriodicAnimateMenu();
554
555 // Update the current time relative to the creation time.
556 void UpdateCurrentTime();
557
558 // This are all menu items. The item at index 0 is the top most and then
559 // they rotate clockwise from there.
560 std::vector<RadialMenuItemVisual*> items_;
561
562 // The screen location of the entire menu.
563 gfx::Rect bounds_;
564
565 // The outer and inner radius of the menu.
566 int outer_radius_;
567 int inner_radius_;
568 int stroke_width_;
569 // The whole menu got rotated by this many degrees.
570 int rotation_;
571
572 // The timer which is used for our events.
573 base::RepeatingTimer<RadialMenuImpl> timer_;
574
575 // The timer "interrupt" can be very imprecise. As such the correct time
576 // gets calculated on use.
577 double current_time_;
578 const base::TimeTicks start_time_;
579
580 // When the menu gets gradually removed from the screen this is true.
581 bool delete_in_progress_;
582 // When the menu deletion is performed this is true. This is put here to
583 // avoid asynchronous animation calls coming in.
584 bool deleting_now_;
585
586 // If true the pointer needs to go exactly over the button to activate,
587 // otherwise anything over or behind the button is fine.
588 int pointer_activation_area_extension_;
589
590 // The active item. A negative value means that none is active.
591 int active_item_;
592
593 DISALLOW_COPY_AND_ASSIGN(RadialMenuImpl);
594 };
595
596 RadialMenuImpl::RadialMenuImpl(const gfx::Point& location,
597 int outer_radius,
598 int inner_radius,
599 double rotation,
600 const RadialMenuItems items,
601 int pointer_activation_area_extension)
602 : outer_radius_(outer_radius),
603 inner_radius_(inner_radius),
604 stroke_width_(kStrokeWidth),
605 rotation_(rotation),
606 current_time_(0.0),
607 start_time_(base::TimeTicks::Now()),
608 delete_in_progress_(false),
609 deleting_now_(false),
610 pointer_activation_area_extension_(pointer_activation_area_extension),
611 active_item_(-1) {
612 // We use 3 times the stroke width for: 1x for the stroke itself and 2x
613 // to offset the wedges of each other (so that they don't touch).
614 bounds_ = gfx::Rect(location.x() - outer_radius_ - 3 * stroke_width_,
615 location.y() - outer_radius_ - 3 * stroke_width_,
616 2 * (outer_radius_ + 3 * stroke_width_),
617 2 * (outer_radius_ + 3 * stroke_width_));
618 // Each item spans this amount of degrees:
619 double circular_span = 360.0 / items.size();
620 // We rotate the menu negative 90 degrees so that the first item is at the
621 // top.
622 rotation -= 90;
623 // Populate the menu with items.
624 for (size_t i = 0; i < items.size(); i++) {
625 // Update the time since considerable time could have already passed.
626 UpdateCurrentTime();
627 // Note: The -90 is to rotate the first segment to the top.
628 items_.push_back(new RadialMenuItemVisual(items[i],
629 current_time_,
630 bounds_,
631 inner_radius_,
632 outer_radius_,
633 stroke_width_,
634 i * circular_span + rotation,
635 circular_span));
636 }
637
638 // Ping the animation code once to initialize.
639 PeriodicAnimateMenu();
640
641 // Kick off our periodic timer to animate the menu.
642 timer_.Start(FROM_HERE,
643 base::TimeDelta::FromMilliseconds(1000 / kFrameRateHz),
644 this,
645 &RadialMenuImpl::PeriodicAnimateMenu);
646 }
647
648 void RadialMenuImpl::SetActiveItem(int index) {
649 DCHECK(index < static_cast<unsigned char>(items_.size()));
650 if (delete_in_progress_ || index == active_item_)
651 return;
652
653 if (active_item_ >= 0)
654 items_[active_item_]->ShowMenuItem();
655
656 active_item_ = index;
657
658 if (active_item_ >= 0)
659 items_[active_item_]->SetMenuItemActive();
660 }
661
662 void RadialMenuImpl::CloseAndDelete(bool immediate_close) {
663 if (delete_in_progress_)
664 return;
665
666 delete_in_progress_ = true;
667
668 if (immediate_close) {
669 delete this;
670 } else {
671 // Fade away all segments of the menu. Once they are gone the menu will
672 // destroy itself.
673 for (size_t i = 0; i < items_.size(); i++)
674 items_[i]->HideMenuItem();
675 }
676 }
677
678 bool RadialMenuImpl::HitMenuItemTest(
679 const gfx::Point& location, int* returned_item, int* returned_count) {
680 if (delete_in_progress_)
681 return false;
682
683 // Get the mid point.
684 double x = bounds_.x() + bounds_.width() / 2;
685 double y = bounds_.y() + bounds_.height() / 2;
686 // The distance of the coordinates.
687 double dx = x - location.x();
688 double dy = y - location.y();
689
690 double distance = sqrt(dx * dx + dy * dy);
691 if (distance < inner_radius_) {
692 SetActiveItem(-1);
693 return false;
694 }
695 int segments = items_.size();
696 // Get the rotation of the point around the mid point.
697 double degree = atan2(dy, dx) * 180 / M_PI;
698 // The segments are offsetted by three rotations:
699 // - 90 degree rotation to get the first element to the top.
700 // - half a segment width to get to the start of the segment
701 // + the callers rotation
702 double first_start_offset = 90 - 360 / segments / 2 + rotation_;
703 // Offset the retrieved rotation and limit it to positive degrees [0..360].
704 int adjusted_degree =
705 static_cast<int>(3600 + degree - first_start_offset) % 360;
706 int item = adjusted_degree / (360 / segments);
707 // For normal buttons we want to only activate it when over the button.
708 // For sliders we want to have the width a bit wider (thus two times the
709 // with of a wedge circle).
710 int step_width = (items_[item]->type() == RadialMenuItem::RADIAL_SLIDER) ?
711 (2 * (outer_radius_ - inner_radius_)) : (outer_radius_ - inner_radius_);
712 // We need to adjust the size relative to the size of the wedge.
713 distance /= items_[item]->RelativeSize();
714 int count = ceil((distance - inner_radius_) / step_width);
715 if (items_[item]->type() == RadialMenuItem::RADIAL_SLIDER) {
716 // TODO(skuhne): Sliders are currently expected to be left / right only.
717 // This can be changed by calculating the distance projected onto the moving
718 // axis.
719 DCHECK((static_cast<int>(items_[item]->center_degree()) + 360) % 180 == 90);
720 if (distance < outer_radius_)
721 items_[item]->SetSliderOffset(gfx::Point(0, 0));
722 else
723 items_[item]->SetSliderOffset(
724 gfx::Point(-(dx + (dx < 0 ? outer_radius_ : -outer_radius_)), 0));
725 }
726 if (items_[item]->IsSeparator() ||
727 items_[item]->type() == RadialMenuItem::RADIAL_DISABLED ||
728 (count > pointer_activation_area_extension_ &&
729 items_[item]->type() != RadialMenuItem::RADIAL_SLIDER)) {
730 SetActiveItem(-1);
731 return false;
732 }
733 SetActiveItem(item);
734 // Transfer the found return information.
735 *returned_item = item;
736 if (items_[item]->type() == RadialMenuItem::RADIAL_SLIDER) {
737 int last_distance = 0;
738 int last_count = 0;
739 items_[item]->GetLastSliderValue(last_count, last_distance);
740 if (last_count != count) {
741 // To avoid value jittering we only accept changes with more then half
742 // step delta.
743 if (abs(last_distance - distance) < step_width / 2 && count)
744 count = last_count;
745 else
746 items_[item]->SetLastSliderValue(count, distance);
747 }
748 *returned_count = count;
749 }
750 return true;
751 }
752
753 RadialMenuImpl::~RadialMenuImpl() {
754 timer_.Stop();
755 // Delete all menu item related resources.
756 while (items_.size()) {
757 RadialMenuItemVisual* item = items_.back();
758 items_.pop_back();
759 delete item;
760 }
761 }
762
763 void RadialMenuImpl::PeriodicAnimateMenu() {
764 if (deleting_now_)
765 return;
766
767 // Since the interrupt is not really happening in "the accurate time", the
768 // time needs to get calculated here.
769 UpdateCurrentTime();
770
771 // We need to check if the menu is still at least partially visible.
772 // This is required for self destruction when we were asked to shut down.
773 bool is_visible = false;
774
775 for (size_t i = 0; i < items_.size(); i++)
776 is_visible |= items_[i]->AdvanceTime(current_time_);
777
778 if (delete_in_progress_ && !is_visible) {
779 deleting_now_ = true;
780 delete this;
781 }
782 }
783
784 void RadialMenuImpl::UpdateCurrentTime() {
785 current_time_ = (base::TimeTicks::Now() - start_time_).InSecondsF();
786 }
787
788 aura::Window* RadialMenuImpl::GetWindow() {
789 for (size_t i = 0; i < items_.size(); i++) {
790 if (items_[i]->GetWindow())
791 return items_[i]->GetWindow();
792 }
793 return NULL;
794 }
795
796 // The class which represents the menu item structure which assembles the menu.
797 class RadialMenuItemImpl: public RadialMenuItem {
798 public:
799 RadialMenuItemImpl(
800 RadialMenuItemType type, gfx::ImageSkia* icon, bool delete_icon);
801
802 virtual RadialMenuItemType Type() OVERRIDE { return type_; }
803 virtual gfx::ImageSkia* Icon() OVERRIDE { return icon_; }
804 virtual bool DeleteIcon() OVERRIDE {return delete_icon_; }
805
806 virtual ~RadialMenuItemImpl();
807
808 private:
809 // The type of the menu.
810 RadialMenuItemType type_;
811 // The image to use for this item.
812 gfx::ImageSkia* icon_;
813 // If true, the icon should get deleted on destruction.
814 bool delete_icon_;
815 };
816
817 RadialMenuItemImpl::RadialMenuItemImpl(
818 RadialMenuItemType type, gfx::ImageSkia* icon, bool delete_icon)
819 : type_(type),
820 icon_(icon),
821 delete_icon_(delete_icon) {}
822
823 RadialMenuItemImpl::~RadialMenuItemImpl() {
824 if (delete_icon_ && icon_)
825 delete icon_;
826 }
827
828 } // namespace
829
830 // static
831 RadialMenu* RadialMenu::CreateRadialMenuInstance(
832 const gfx::Point& location,
833 int outer_radius,
834 int inner_radius,
835 double rotation,
836 const RadialMenuItems items,
837 int pointer_activation_area_extension,
838 bool clip_menu_to_screen) {
839 DCHECK(pointer_activation_area_extension >= 1);
840 int min_radius = outer_radius;
841 if (clip_menu_to_screen) {
842 // Check that the menu stays within the visible screen by resizing if
843 // necessary.
844 gfx::Rect screen = gfx::Screen::GetDisplayNearestPoint(location).bounds();
845 if (location.x() - screen.x() < min_radius)
846 min_radius = location.x() - screen.x();
847 if (location.y() - screen.y() < min_radius)
848 min_radius = location.y() - screen.y();
849 if (screen.right() - location.x() < min_radius)
850 min_radius = screen.right() - location.x();
851 if (screen.bottom() - location.y() < min_radius)
852 min_radius = screen.bottom() - location.y();
853 if (min_radius < 48)
854 min_radius = 48;
855 inner_radius = (inner_radius * min_radius) / outer_radius;
856 }
857
858 // The animation will delete itself when it's finished or when the tab
859 // contents is hidden or destroyed.
860 return new RadialMenuImpl(
861 location,
862 min_radius,
863 inner_radius,
864 rotation,
865 items,
866 pointer_activation_area_extension);
867 }
868
869 // static
870 RadialMenuItem* RadialMenuItem::CreateRadialMenuItemInstance(
871 RadialMenuItem::RadialMenuItemType type,
872 gfx::ImageSkia* icon,
873 bool delete_icon) {
874 return new RadialMenuItemImpl(type, icon, delete_icon);
875 }
876
877 } // namespace views
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698