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/wm/overview/window_selector_item.h" 6 7 #include "ash/screen_util.h" 8 #include "ash/shell.h" 9 #include "ash/shell_window_ids.h" 10 #include "ash/wm/overview/scoped_transform_overview_window.h" 11 #include "ash/wm/overview/transparent_activate_window_button.h" 12 #include "base/auto_reset.h" 13 #include "grit/ash_resources.h" 14 #include "ui/aura/window.h" 15 #include "ui/base/resource/resource_bundle.h" 16 #include "ui/compositor/scoped_layer_animation_settings.h" 17 #include "ui/views/controls/button/image_button.h" 18 #include "ui/views/controls/label.h" 19 #include "ui/views/layout/box_layout.h" 20 #include "ui/views/widget/widget.h" 21 22 namespace ash { 23 24 namespace { 25 26 views::Widget* CreateCloseWindowButton(aura::Window* root_window, 27 views::ButtonListener* listener) { 28 views::Widget* widget = new views::Widget; 29 views::Widget::InitParams params; 30 params.type = views::Widget::InitParams::TYPE_POPUP; 31 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 32 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; 33 params.parent = 34 Shell::GetContainer(root_window, ash::kShellWindowId_OverlayContainer); 35 widget->set_focus_on_creation(false); 36 widget->Init(params); 37 views::ImageButton* button = new views::ImageButton(listener); 38 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 39 button->SetImage(views::CustomButton::STATE_NORMAL, 40 rb.GetImageSkiaNamed(IDR_AURA_WINDOW_OVERVIEW_CLOSE)); 41 button->SetImage(views::CustomButton::STATE_HOVERED, 42 rb.GetImageSkiaNamed(IDR_AURA_WINDOW_OVERVIEW_CLOSE_H)); 43 button->SetImage(views::CustomButton::STATE_PRESSED, 44 rb.GetImageSkiaNamed(IDR_AURA_WINDOW_OVERVIEW_CLOSE_P)); 45 widget->SetContentsView(button); 46 widget->SetSize(rb.GetImageSkiaNamed(IDR_AURA_WINDOW_OVERVIEW_CLOSE)->size()); 47 widget->Show(); 48 return widget; 49 } 50 51 } // namespace 52 53 // In the conceptual overview table, the window margin is the space reserved 54 // around the window within the cell. This margin does not overlap so the 55 // closest distance between adjacent windows will be twice this amount. 56 static const int kWindowMargin = 30; 57 58 // Foreground label color. 59 static const SkColor kLabelColor = SK_ColorWHITE; 60 61 // Background label color. 62 static const SkColor kLabelBackground = SK_ColorTRANSPARENT; 63 64 // Label shadow color. 65 static const SkColor kLabelShadow = 0xB0000000; 66 67 // Vertical padding for the label, both over and beneath it. 68 static const int kVerticalLabelPadding = 20; 69 70 // Solid shadow length from the label 71 static const int kVerticalShadowOffset = 1; 72 73 // Amount of blur applied to the label shadow 74 static const int kShadowBlur = 10; 75 76 const int WindowSelectorItem::kFadeInMilliseconds = 80; 77 78 // Opacity for dimmed items. 79 static const float kDimmedItemOpacity = 0.5f; 80 81 WindowSelectorItem::WindowSelectorItem() 82 : dimmed_(false), 83 root_window_(NULL), 84 in_bounds_update_(false), 85 window_label_view_(NULL) { 86 } 87 88 WindowSelectorItem::~WindowSelectorItem() { 89 } 90 91 void WindowSelectorItem::RemoveWindow(const aura::Window* window) { 92 // If empty WindowSelectorItem will be destroyed immediately after this by 93 // its owner. 94 if (empty()) 95 return; 96 window_label_.reset(); 97 UpdateWindowLabels(target_bounds_, root_window_, false); 98 UpdateCloseButtonBounds(root_window_, false); 99 } 100 101 void WindowSelectorItem::SetBounds(aura::Window* root_window, 102 const gfx::Rect& target_bounds, 103 bool animate) { 104 if (in_bounds_update_) 105 return; 106 base::AutoReset<bool> auto_reset_in_bounds_update(&in_bounds_update_, true); 107 root_window_ = root_window; 108 target_bounds_ = target_bounds; 109 110 // Set the bounds of the transparent window handler to cover the entire 111 // bounding box area. 112 if (!activate_window_button_) { 113 activate_window_button_.reset( 114 new TransparentActivateWindowButton(SelectionWindow())); 115 } 116 activate_window_button_->SetBounds(target_bounds); 117 118 UpdateWindowLabels(target_bounds, root_window, animate); 119 120 gfx::Rect inset_bounds(target_bounds); 121 inset_bounds.Inset(kWindowMargin, kWindowMargin); 122 SetItemBounds(root_window, inset_bounds, animate); 123 UpdateCloseButtonBounds(root_window, animate); 124 } 125 126 void WindowSelectorItem::RecomputeWindowTransforms() { 127 if (in_bounds_update_ || target_bounds_.IsEmpty()) 128 return; 129 DCHECK(root_window_); 130 base::AutoReset<bool> auto_reset_in_bounds_update(&in_bounds_update_, true); 131 gfx::Rect inset_bounds(target_bounds_); 132 inset_bounds.Inset(kWindowMargin, kWindowMargin); 133 SetItemBounds(root_window_, inset_bounds, false); 134 UpdateCloseButtonBounds(root_window_, false); 135 } 136 137 void WindowSelectorItem::SendFocusAlert() const { 138 activate_window_button_->SendFocusAlert(); 139 } 140 141 void WindowSelectorItem::SetDimmed(bool dimmed) { 142 dimmed_ = dimmed; 143 SetOpacity(dimmed ? kDimmedItemOpacity : 1.0f); 144 } 145 146 void WindowSelectorItem::ButtonPressed(views::Button* sender, 147 const ui::Event& event) { 148 views::Widget::GetWidgetForNativeView(SelectionWindow())->Close(); 149 } 150 151 void WindowSelectorItem::OnWindowTitleChanged(aura::Window* window) { 152 // TODO(flackr): Maybe add the new title to a vector of titles so that we can 153 // filter any of the titles the window had while in the overview session. 154 if (window == SelectionWindow()) 155 window_label_view_->SetText(window->title()); 156 } 157 158 void WindowSelectorItem::UpdateCloseButtonBounds(aura::Window* root_window, 159 bool animate) { 160 gfx::RectF align_bounds(SelectionWindow()->layer()->bounds()); 161 gfx::Transform window_transform; 162 window_transform.Translate(align_bounds.x(), align_bounds.y()); 163 window_transform.PreconcatTransform(SelectionWindow()->layer()-> 164 GetTargetTransform()); 165 window_transform.Translate(-align_bounds.x(), -align_bounds.y()); 166 window_transform.TransformRect(&align_bounds); 167 gfx::Rect target_bounds = ToEnclosingRect(align_bounds); 168 169 gfx::Transform close_button_transform; 170 close_button_transform.Translate(target_bounds.right(), target_bounds.y()); 171 172 // If the root window has changed, force the close button to be recreated 173 // and faded in on the new root window. 174 if (close_button_ && 175 close_button_->GetNativeWindow()->GetRootWindow() != root_window) { 176 close_button_.reset(); 177 } 178 179 if (!close_button_) { 180 close_button_.reset(CreateCloseWindowButton(root_window, this)); 181 gfx::Rect close_button_rect(close_button_->GetNativeWindow()->bounds()); 182 // Align the center of the button with position (0, 0) so that the 183 // translate transform does not need to take the button dimensions into 184 // account. 185 close_button_rect.set_x(-close_button_rect.width() / 2); 186 close_button_rect.set_y(-close_button_rect.height() / 2); 187 close_button_->GetNativeWindow()->SetBounds(close_button_rect); 188 close_button_->GetNativeWindow()->SetTransform(close_button_transform); 189 // The close button is initialized when entering overview, fade the button 190 // in after the window should be in place. 191 ui::Layer* layer = close_button_->GetNativeWindow()->layer(); 192 layer->SetOpacity(0); 193 layer->GetAnimator()->StopAnimating(); 194 layer->GetAnimator()->SchedulePauseForProperties( 195 base::TimeDelta::FromMilliseconds( 196 ScopedTransformOverviewWindow::kTransitionMilliseconds), 197 ui::LayerAnimationElement::OPACITY); 198 { 199 ui::ScopedLayerAnimationSettings settings(layer->GetAnimator()); 200 settings.SetPreemptionStrategy( 201 ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS); 202 settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds( 203 WindowSelectorItem::kFadeInMilliseconds)); 204 layer->SetOpacity(1); 205 } 206 } else { 207 if (animate) { 208 ui::ScopedLayerAnimationSettings settings( 209 close_button_->GetNativeWindow()->layer()->GetAnimator()); 210 settings.SetPreemptionStrategy( 211 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); 212 settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds( 213 ScopedTransformOverviewWindow::kTransitionMilliseconds)); 214 close_button_->GetNativeWindow()->SetTransform(close_button_transform); 215 } else { 216 close_button_->GetNativeWindow()->SetTransform(close_button_transform); 217 } 218 } 219 } 220 221 void WindowSelectorItem::SetOpacity(float opacity) { 222 window_label_->GetNativeWindow()->layer()->SetOpacity(opacity); 223 close_button_->GetNativeWindow()->layer()->SetOpacity(opacity); 224 } 225 226 void WindowSelectorItem::UpdateWindowLabels(const gfx::Rect& window_bounds, 227 aura::Window* root_window, 228 bool animate) { 229 gfx::Rect converted_bounds = ScreenUtil::ConvertRectFromScreen(root_window, 230 window_bounds); 231 gfx::Rect label_bounds(converted_bounds.x(), 232 converted_bounds.bottom(), 233 converted_bounds.width(), 234 0); 235 236 // If the root window has changed, force the window label to be recreated 237 // and faded in on the new root window. 238 if (window_label_ && 239 window_label_->GetNativeWindow()->GetRootWindow() != root_window) { 240 window_label_.reset(); 241 } 242 243 if (!window_label_) { 244 CreateWindowLabel(SelectionWindow()->title()); 245 label_bounds.set_height(window_label_view_->GetPreferredSize().height()); 246 label_bounds.set_y(label_bounds.y() - window_label_view_-> 247 GetPreferredSize().height()); 248 window_label_->GetNativeWindow()->SetBounds(label_bounds); 249 ui::Layer* layer = window_label_->GetNativeWindow()->layer(); 250 251 layer->SetOpacity(0); 252 layer->GetAnimator()->StopAnimating(); 253 254 layer->GetAnimator()->SchedulePauseForProperties( 255 base::TimeDelta::FromMilliseconds( 256 ScopedTransformOverviewWindow::kTransitionMilliseconds), 257 ui::LayerAnimationElement::OPACITY); 258 259 ui::ScopedLayerAnimationSettings settings(layer->GetAnimator()); 260 settings.SetPreemptionStrategy( 261 ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS); 262 settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds( 263 kFadeInMilliseconds)); 264 layer->SetOpacity(1); 265 } else { 266 label_bounds.set_height(window_label_-> 267 GetContentsView()->GetPreferredSize().height()); 268 label_bounds.set_y(label_bounds.y() - window_label_-> 269 GetContentsView()->GetPreferredSize().height()); 270 if (animate) { 271 ui::ScopedLayerAnimationSettings settings( 272 window_label_->GetNativeWindow()->layer()->GetAnimator()); 273 settings.SetPreemptionStrategy( 274 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); 275 settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds( 276 ScopedTransformOverviewWindow::kTransitionMilliseconds)); 277 window_label_->GetNativeWindow()->SetBounds(label_bounds); 278 } else { 279 window_label_->GetNativeWindow()->SetBounds(label_bounds); 280 } 281 } 282 } 283 284 void WindowSelectorItem::CreateWindowLabel(const base::string16& title) { 285 window_label_.reset(new views::Widget); 286 views::Widget::InitParams params; 287 params.type = views::Widget::InitParams::TYPE_POPUP; 288 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 289 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; 290 params.parent = 291 Shell::GetContainer(root_window_, ash::kShellWindowId_OverlayContainer); 292 params.accept_events = false; 293 params.visible_on_all_workspaces = true; 294 window_label_->set_focus_on_creation(false); 295 window_label_->Init(params); 296 window_label_view_ = new views::Label; 297 window_label_view_->SetEnabledColor(kLabelColor); 298 window_label_view_->SetBackgroundColor(kLabelBackground); 299 window_label_view_->SetShadows(gfx::ShadowValues( 300 1, 301 gfx::ShadowValue( 302 gfx::Point(0, kVerticalShadowOffset), kShadowBlur, kLabelShadow))); 303 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 304 window_label_view_->SetFontList( 305 bundle.GetFontList(ui::ResourceBundle::BoldFont)); 306 window_label_view_->SetText(title); 307 views::BoxLayout* layout = new views::BoxLayout(views::BoxLayout::kVertical, 308 0, 309 kVerticalLabelPadding, 310 0); 311 window_label_view_->SetLayoutManager(layout); 312 window_label_->SetContentsView(window_label_view_); 313 window_label_->Show(); 314 } 315 316 } // namespace ash 317