1 // Copyright 2013 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 "ash/frame/caption_buttons/frame_caption_button_container_view.h" 6 7 #include <cmath> 8 #include <map> 9 10 #include "ash/ash_switches.h" 11 #include "ash/frame/caption_buttons/frame_caption_button.h" 12 #include "ash/frame/caption_buttons/frame_size_button.h" 13 #include "ash/metrics/user_metrics_recorder.h" 14 #include "ash/shell.h" 15 #include "ash/wm/maximize_mode/maximize_mode_controller.h" 16 #include "grit/ui_strings.h" // Accessibility names 17 #include "ui/base/hit_test.h" 18 #include "ui/base/l10n/l10n_util.h" 19 #include "ui/compositor/scoped_animation_duration_scale_mode.h" 20 #include "ui/gfx/canvas.h" 21 #include "ui/gfx/insets.h" 22 #include "ui/gfx/point.h" 23 #include "ui/views/widget/widget.h" 24 #include "ui/views/widget/widget_delegate.h" 25 26 namespace ash { 27 28 namespace { 29 30 // Converts |point| from |src| to |dst| and hittests against |dst|. 31 bool ConvertPointToViewAndHitTest(const views::View* src, 32 const views::View* dst, 33 const gfx::Point& point) { 34 gfx::Point converted(point); 35 views::View::ConvertPointToTarget(src, dst, &converted); 36 return dst->HitTestPoint(converted); 37 } 38 39 } // namespace 40 41 // static 42 const char FrameCaptionButtonContainerView::kViewClassName[] = 43 "FrameCaptionButtonContainerView"; 44 45 FrameCaptionButtonContainerView::FrameCaptionButtonContainerView( 46 views::Widget* frame, 47 MinimizeAllowed minimize_allowed) 48 : frame_(frame), 49 minimize_button_(NULL), 50 size_button_(NULL), 51 close_button_(NULL) { 52 // Insert the buttons left to right. 53 minimize_button_ = new FrameCaptionButton(this, CAPTION_BUTTON_ICON_MINIMIZE); 54 minimize_button_->SetAccessibleName( 55 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE)); 56 minimize_button_->SetVisible(minimize_allowed == MINIMIZE_ALLOWED); 57 AddChildView(minimize_button_); 58 59 size_button_ = new FrameSizeButton(this, frame, this); 60 size_button_->SetAccessibleName( 61 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE)); 62 UpdateSizeButtonVisibility(); 63 AddChildView(size_button_); 64 65 close_button_ = new FrameCaptionButton(this, CAPTION_BUTTON_ICON_CLOSE); 66 close_button_->SetAccessibleName( 67 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE)); 68 AddChildView(close_button_); 69 } 70 71 FrameCaptionButtonContainerView::~FrameCaptionButtonContainerView() { 72 } 73 74 void FrameCaptionButtonContainerView::SetButtonImages( 75 CaptionButtonIcon icon, 76 int icon_image_id, 77 int inactive_icon_image_id, 78 int hovered_background_image_id, 79 int pressed_background_image_id) { 80 button_icon_id_map_[icon] = ButtonIconIds(icon_image_id, 81 inactive_icon_image_id, 82 hovered_background_image_id, 83 pressed_background_image_id); 84 FrameCaptionButton* buttons[] = { 85 minimize_button_, size_button_, close_button_ 86 }; 87 for (size_t i = 0; i < arraysize(buttons); ++i) { 88 if (buttons[i]->icon() == icon) { 89 buttons[i]->SetImages(icon, 90 FrameCaptionButton::ANIMATE_NO, 91 icon_image_id, 92 inactive_icon_image_id, 93 hovered_background_image_id, 94 pressed_background_image_id); 95 } 96 } 97 } 98 99 void FrameCaptionButtonContainerView::SetPaintAsActive(bool paint_as_active) { 100 minimize_button_->set_paint_as_active(paint_as_active); 101 size_button_->set_paint_as_active(paint_as_active); 102 close_button_->set_paint_as_active(paint_as_active); 103 } 104 105 void FrameCaptionButtonContainerView::ResetWindowControls() { 106 SetButtonsToNormal(ANIMATE_NO); 107 } 108 109 int FrameCaptionButtonContainerView::NonClientHitTest( 110 const gfx::Point& point) const { 111 if (close_button_->visible() && 112 ConvertPointToViewAndHitTest(this, close_button_, point)) { 113 return HTCLOSE; 114 } else if (size_button_->visible() && 115 ConvertPointToViewAndHitTest(this, size_button_, point)) { 116 return HTMAXBUTTON; 117 } else if (minimize_button_->visible() && 118 ConvertPointToViewAndHitTest(this, minimize_button_, point)) { 119 return HTMINBUTTON; 120 } 121 return HTNOWHERE; 122 } 123 124 void FrameCaptionButtonContainerView::UpdateSizeButtonVisibility() { 125 size_button_->SetVisible( 126 !Shell::GetInstance()->maximize_mode_controller()-> 127 IsMaximizeModeWindowManagerEnabled() && 128 frame_->widget_delegate()->CanMaximize()); 129 } 130 131 gfx::Size FrameCaptionButtonContainerView::GetPreferredSize() const { 132 int width = 0; 133 for (int i = 0; i < child_count(); ++i) { 134 const views::View* child = child_at(i); 135 if (child->visible()) 136 width += child_at(i)->GetPreferredSize().width(); 137 } 138 return gfx::Size(width, close_button_->GetPreferredSize().height()); 139 } 140 141 void FrameCaptionButtonContainerView::Layout() { 142 int x = 0; 143 for (int i = 0; i < child_count(); ++i) { 144 views::View* child = child_at(i); 145 if (!child->visible()) 146 continue; 147 148 gfx::Size size = child->GetPreferredSize(); 149 child->SetBounds(x, 0, size.width(), size.height()); 150 x += size.width(); 151 } 152 } 153 154 const char* FrameCaptionButtonContainerView::GetClassName() const { 155 return kViewClassName; 156 } 157 158 void FrameCaptionButtonContainerView::SetButtonIcon(FrameCaptionButton* button, 159 CaptionButtonIcon icon, 160 Animate animate) { 161 // The early return is dependant on |animate| because callers use 162 // SetButtonIcon() with ANIMATE_NO to progress |button|'s crossfade animation 163 // to the end. 164 if (button->icon() == icon && 165 (animate == ANIMATE_YES || !button->IsAnimatingImageSwap())) { 166 return; 167 } 168 169 FrameCaptionButton::Animate fcb_animate = (animate == ANIMATE_YES) ? 170 FrameCaptionButton::ANIMATE_YES : FrameCaptionButton::ANIMATE_NO; 171 std::map<CaptionButtonIcon, ButtonIconIds>::const_iterator it = 172 button_icon_id_map_.find(icon); 173 if (it != button_icon_id_map_.end()) { 174 button->SetImages(icon, 175 fcb_animate, 176 it->second.icon_image_id, 177 it->second.inactive_icon_image_id, 178 it->second.hovered_background_image_id, 179 it->second.pressed_background_image_id); 180 } 181 } 182 183 void FrameCaptionButtonContainerView::ButtonPressed(views::Button* sender, 184 const ui::Event& event) { 185 // When shift-clicking, slow down animations for visual debugging. 186 // We used to do this via an event filter that looked for the shift key being 187 // pressed but this interfered with several normal keyboard shortcuts. 188 scoped_ptr<ui::ScopedAnimationDurationScaleMode> slow_duration_mode; 189 if (event.IsShiftDown()) { 190 slow_duration_mode.reset(new ui::ScopedAnimationDurationScaleMode( 191 ui::ScopedAnimationDurationScaleMode::SLOW_DURATION)); 192 } 193 194 // Abort any animations of the button icons. 195 SetButtonsToNormal(ANIMATE_NO); 196 197 ash::UserMetricsAction action = 198 ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MINIMIZE; 199 if (sender == minimize_button_) { 200 frame_->Minimize(); 201 } else if (sender == size_button_) { 202 if (frame_->IsFullscreen()) { // Can be clicked in immersive fullscreen. 203 frame_->SetFullscreen(false); 204 action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_EXIT_FULLSCREEN; 205 } else if (frame_->IsMaximized()) { 206 frame_->Restore(); 207 action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_RESTORE; 208 } else { 209 frame_->Maximize(); 210 action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MAXIMIZE; 211 } 212 } else if (sender == close_button_) { 213 frame_->Close(); 214 action = ash::UMA_WINDOW_CLOSE_BUTTON_CLICK; 215 } else { 216 return; 217 } 218 ash::Shell::GetInstance()->metrics()->RecordUserMetricsAction(action); 219 } 220 221 bool FrameCaptionButtonContainerView::IsMinimizeButtonVisible() const { 222 return minimize_button_->visible(); 223 } 224 225 void FrameCaptionButtonContainerView::SetButtonsToNormal(Animate animate) { 226 SetButtonIcons(CAPTION_BUTTON_ICON_MINIMIZE, CAPTION_BUTTON_ICON_CLOSE, 227 animate); 228 minimize_button_->SetState(views::Button::STATE_NORMAL); 229 size_button_->SetState(views::Button::STATE_NORMAL); 230 close_button_->SetState(views::Button::STATE_NORMAL); 231 } 232 233 void FrameCaptionButtonContainerView::SetButtonIcons( 234 CaptionButtonIcon minimize_button_icon, 235 CaptionButtonIcon close_button_icon, 236 Animate animate) { 237 SetButtonIcon(minimize_button_, minimize_button_icon, animate); 238 SetButtonIcon(close_button_, close_button_icon, animate); 239 } 240 241 const FrameCaptionButton* FrameCaptionButtonContainerView::GetButtonClosestTo( 242 const gfx::Point& position_in_screen) const { 243 // Since the buttons all have the same size, the closest button is the button 244 // with the center point closest to |position_in_screen|. 245 // TODO(pkotwicz): Make the caption buttons not overlap. 246 gfx::Point position(position_in_screen); 247 views::View::ConvertPointFromScreen(this, &position); 248 249 FrameCaptionButton* buttons[] = { 250 minimize_button_, size_button_, close_button_ 251 }; 252 int min_squared_distance = INT_MAX; 253 FrameCaptionButton* closest_button = NULL; 254 for (size_t i = 0; i < arraysize(buttons); ++i) { 255 FrameCaptionButton* button = buttons[i]; 256 if (!button->visible()) 257 continue; 258 259 gfx::Point center_point = button->GetLocalBounds().CenterPoint(); 260 views::View::ConvertPointToTarget(button, this, ¢er_point); 261 int squared_distance = static_cast<int>( 262 pow(static_cast<double>(position.x() - center_point.x()), 2) + 263 pow(static_cast<double>(position.y() - center_point.y()), 2)); 264 if (squared_distance < min_squared_distance) { 265 min_squared_distance = squared_distance; 266 closest_button = button; 267 } 268 } 269 return closest_button; 270 } 271 272 void FrameCaptionButtonContainerView::SetHoveredAndPressedButtons( 273 const FrameCaptionButton* to_hover, 274 const FrameCaptionButton* to_press) { 275 FrameCaptionButton* buttons[] = { 276 minimize_button_, size_button_, close_button_ 277 }; 278 for (size_t i = 0; i < arraysize(buttons); ++i) { 279 FrameCaptionButton* button = buttons[i]; 280 views::Button::ButtonState new_state = views::Button::STATE_NORMAL; 281 if (button == to_hover) 282 new_state = views::Button::STATE_HOVERED; 283 else if (button == to_press) 284 new_state = views::Button::STATE_PRESSED; 285 button->SetState(new_state); 286 } 287 } 288 289 FrameCaptionButtonContainerView::ButtonIconIds::ButtonIconIds() 290 : icon_image_id(-1), 291 inactive_icon_image_id(-1), 292 hovered_background_image_id(-1), 293 pressed_background_image_id(-1) { 294 } 295 296 FrameCaptionButtonContainerView::ButtonIconIds::ButtonIconIds( 297 int icon_id, 298 int inactive_icon_id, 299 int hovered_background_id, 300 int pressed_background_id) 301 : icon_image_id(icon_id), 302 inactive_icon_image_id(inactive_icon_id), 303 hovered_background_image_id(hovered_background_id), 304 pressed_background_image_id(pressed_background_id) { 305 } 306 307 FrameCaptionButtonContainerView::ButtonIconIds::~ButtonIconIds() { 308 } 309 310 } // namespace ash 311