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.
5 #include "ash/wm/caption_buttons/frame_caption_button_container_view.h"
7 #include "ash/ash_switches.h"
9 #include "ash/shell_delegate.h"
10 #include "ash/wm/caption_buttons/alternate_frame_caption_button.h"
11 #include "ash/wm/caption_buttons/frame_maximize_button.h"
12 #include "ash/wm/window_state.h"
13 #include "grit/ash_resources.h"
14 #include "grit/ui_strings.h" // Accessibility names
15 #include "ui/base/hit_test.h"
16 #include "ui/base/l10n/l10n_util.h"
17 #include "ui/base/resource/resource_bundle.h"
18 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
19 #include "ui/gfx/canvas.h"
20 #include "ui/views/border.h"
21 #include "ui/views/controls/button/image_button.h"
22 #include "ui/views/widget/widget.h"
23 #include "ui/views/widget/widget_delegate.h"
29 // Constants for normal button style -------------------------------------------
31 // The distance between buttons. AlternateFrameCaptionButton::GetXOverlap() is
32 // used to compute the distance between buttons for the alternate button style.
33 const int kDistanceBetweenButtons = -1;
35 // Constants for alternate button style ----------------------------------------
37 // Spacings between the buttons and the view's top and bottom borders (if any).
38 const int kAlternateStyleShortHeaderTopInset = 0;
39 const int kAlternateStyleTallHeaderTopInset = 2;
40 const int kAlternateStyleShortHeaderBottomInset = 0;
41 const int kAlternateStyleTallHeaderBottomInset = 1;
43 // Ideal spacing between:
44 // - Right edge of the leftmost button's overlappable region and the view's left
46 // - Left edge of the rightmost button's overlappable region and the view's
48 // Used in GetLeftInset() and GetRightInset().
49 const int kAlternateStyleSideInset = 5;
51 // Converts |point| from |src| to |dst| and hittests against |dst|.
52 bool ConvertPointToViewAndHitTest(const views::View* src,
53 const views::View* dst,
54 const gfx::Point& point) {
55 gfx::Point converted(point);
56 views::View::ConvertPointToTarget(src, dst, &converted);
57 return dst->HitTestPoint(converted);
63 const char FrameCaptionButtonContainerView::kViewClassName[] =
64 "FrameCaptionButtonContainerView";
66 FrameCaptionButtonContainerView::FrameCaptionButtonContainerView(
68 MinimizeAllowed minimize_allowed)
70 header_style_(HEADER_STYLE_SHORT),
71 minimize_button_(NULL),
74 bool alternate_style = switches::UseAlternateFrameCaptionButtonStyle();
76 // Insert the buttons left to right.
77 if (alternate_style) {
78 minimize_button_ = new AlternateFrameCaptionButton(this,
79 AlternateFrameCaptionButton::ACTION_MINIMIZE);
80 size_button_ = new AlternateFrameCaptionButton(this,
81 AlternateFrameCaptionButton::ACTION_MAXIMIZE_RESTORE);
82 close_button_ = new AlternateFrameCaptionButton(this,
83 AlternateFrameCaptionButton::ACTION_CLOSE);
85 minimize_button_ = new views::ImageButton(this);
86 size_button_ = new FrameMaximizeButton(this, frame);
87 close_button_ = new views::ImageButton(this);
90 minimize_button_->SetAccessibleName(
91 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE));
92 // Hide |minimize_button_| when using the non-alternate button style because
93 // |size_button_| is capable of minimizing in this case.
94 // TODO(pkotwicz): We should probably show the minimize button when in
95 // "always maximized" mode.
96 minimize_button_->SetVisible(
97 minimize_allowed == MINIMIZE_ALLOWED &&
98 (alternate_style || !frame_->widget_delegate()->CanMaximize()));
99 AddChildView(minimize_button_);
101 size_button_->SetAccessibleName(
102 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE));
103 size_button_->SetVisible(frame_->widget_delegate()->CanMaximize());
104 AddChildView(size_button_);
106 close_button_->SetAccessibleName(
107 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
108 AddChildView(close_button_);
110 button_separator_ = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
111 IDR_AURA_WINDOW_BUTTON_SEPARATOR).AsImageSkia();
114 FrameCaptionButtonContainerView::~FrameCaptionButtonContainerView() {
117 void FrameCaptionButtonContainerView::ResetWindowControls() {
118 minimize_button_->SetState(views::CustomButton::STATE_NORMAL);
119 size_button_->SetState(views::CustomButton::STATE_NORMAL);
122 int FrameCaptionButtonContainerView::NonClientHitTest(
123 const gfx::Point& point) const {
124 if (close_button_->visible() &&
125 ConvertPointToViewAndHitTest(this, close_button_, point)) {
127 } else if (size_button_->visible() &&
128 ConvertPointToViewAndHitTest(this, size_button_, point)) {
130 } else if (minimize_button_->visible() &&
131 ConvertPointToViewAndHitTest(this, minimize_button_, point)) {
137 gfx::Size FrameCaptionButtonContainerView::GetPreferredSize() {
138 int button_separation = GetDistanceBetweenButtons();
141 bool first_visible = true;
142 for (int i = 0; i < child_count(); ++i) {
143 views::View* child = child_at(i);
144 if (!child->visible())
147 width += child_at(i)->GetPreferredSize().width();
149 width += button_separation;
150 first_visible = false;
152 gfx::Insets insets(GetInsets());
154 width + insets.width() + GetLeftInset() + GetRightInset(),
155 close_button_->GetPreferredSize().height() + insets.height());
158 void FrameCaptionButtonContainerView::Layout() {
159 if (switches::UseAlternateFrameCaptionButtonStyle()) {
160 int top_inset = kAlternateStyleShortHeaderTopInset;
161 int bottom_inset = kAlternateStyleShortHeaderBottomInset;
162 if (header_style_ == HEADER_STYLE_TALL) {
163 top_inset = kAlternateStyleTallHeaderTopInset;
164 bottom_inset = kAlternateStyleTallHeaderBottomInset;
167 minimize_button_->set_border(
168 views::Border::CreateEmptyBorder(top_inset, 0, bottom_inset, 0));
169 size_button_->set_border(
170 views::Border::CreateEmptyBorder(top_inset, 0, bottom_inset, 0));
171 close_button_->set_border(
172 views::Border::CreateEmptyBorder(top_inset, 0, bottom_inset, 0));
174 SetButtonImages(minimize_button_,
175 IDR_AURA_WINDOW_MINIMIZE_SHORT,
176 IDR_AURA_WINDOW_MINIMIZE_SHORT_H,
177 IDR_AURA_WINDOW_MINIMIZE_SHORT_P);
179 if (header_style_ == HEADER_STYLE_MAXIMIZED_HOSTED_APP) {
180 SetButtonImages(size_button_,
181 IDR_AURA_WINDOW_FULLSCREEN_RESTORE,
182 IDR_AURA_WINDOW_FULLSCREEN_RESTORE_H,
183 IDR_AURA_WINDOW_FULLSCREEN_RESTORE_P);
184 SetButtonImages(close_button_,
185 IDR_AURA_WINDOW_FULLSCREEN_CLOSE,
186 IDR_AURA_WINDOW_FULLSCREEN_CLOSE_H,
187 IDR_AURA_WINDOW_FULLSCREEN_CLOSE_P);
188 } else if (header_style_ == HEADER_STYLE_SHORT) {
189 // The new assets only make sense if the window is maximized or fullscreen
190 // because we usually use a black header in this case.
191 if ((frame_->IsMaximized() || frame_->IsFullscreen()) &&
193 frame_->GetNativeWindow())->tracked_by_workspace()) {
194 SetButtonImages(size_button_,
195 IDR_AURA_WINDOW_MAXIMIZED_RESTORE2,
196 IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_H,
197 IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_P);
198 SetButtonImages(close_button_,
199 IDR_AURA_WINDOW_MAXIMIZED_CLOSE2,
200 IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_H,
201 IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_P);
203 SetButtonImages(size_button_,
204 IDR_AURA_WINDOW_MAXIMIZED_RESTORE,
205 IDR_AURA_WINDOW_MAXIMIZED_RESTORE_H,
206 IDR_AURA_WINDOW_MAXIMIZED_RESTORE_P);
207 SetButtonImages(close_button_,
208 IDR_AURA_WINDOW_MAXIMIZED_CLOSE,
209 IDR_AURA_WINDOW_MAXIMIZED_CLOSE_H,
210 IDR_AURA_WINDOW_MAXIMIZED_CLOSE_P);
213 SetButtonImages(size_button_,
214 IDR_AURA_WINDOW_MAXIMIZE,
215 IDR_AURA_WINDOW_MAXIMIZE_H,
216 IDR_AURA_WINDOW_MAXIMIZE_P);
217 SetButtonImages(close_button_,
218 IDR_AURA_WINDOW_CLOSE,
219 IDR_AURA_WINDOW_CLOSE_H,
220 IDR_AURA_WINDOW_CLOSE_P);
224 gfx::Insets insets(GetInsets());
225 int x = insets.left() + GetLeftInset();
226 int y_inset = insets.top();
227 int button_separation = GetDistanceBetweenButtons();
228 for (int i = 0; i < child_count(); ++i) {
229 views::View* child = child_at(i);
230 if (!child->visible())
233 gfx::Size size = child->GetPreferredSize();
234 child->SetBounds(x, y_inset, size.width(), size.height());
236 // Do not allow |child| to paint over the left border.
237 child->set_clip_insets(
238 gfx::Insets(0, std::max(0, insets.left() - x), 0, 0));
240 x += size.width() + button_separation;
244 const char* FrameCaptionButtonContainerView::GetClassName() const {
245 return kViewClassName;
248 void FrameCaptionButtonContainerView::OnPaint(gfx::Canvas* canvas) {
249 views::View::OnPaint(canvas);
251 // The alternate button style and AppNonClientFrameViewAsh do not paint the
253 if (header_style_ != HEADER_STYLE_MAXIMIZED_HOSTED_APP &&
254 !switches::UseAlternateFrameCaptionButtonStyle()) {
255 // We should have at most two visible buttons. The button separator is
256 // always painted underneath the close button regardless of whether a
257 // button other than the close button is visible.
258 gfx::Rect divider(close_button_->bounds().origin(),
259 button_separator_.size());
260 canvas->DrawImageInt(button_separator_,
261 GetMirroredXForRect(divider),
266 int FrameCaptionButtonContainerView::GetDistanceBetweenButtons() const {
267 if (switches::UseAlternateFrameCaptionButtonStyle())
268 return AlternateFrameCaptionButton::GetXOverlap() * -2;
269 return kDistanceBetweenButtons;
272 int FrameCaptionButtonContainerView::GetLeftInset() const {
273 if (switches::UseAlternateFrameCaptionButtonStyle()) {
274 // If using the alternate button style and there is a border, clip the
275 // left overlappable region of the leftmost button to
276 // |kAlternateStyleSideInset|.
277 // Otherwise, allow enough room for the entire left overlappable region of
278 // the leftmost button to fit in the view.
279 if (border() && border()->GetInsets().left()) {
280 return kAlternateStyleSideInset -
281 AlternateFrameCaptionButton::GetXOverlap();
287 int FrameCaptionButtonContainerView::GetRightInset() const {
288 if (switches::UseAlternateFrameCaptionButtonStyle()) {
289 // Always clip the right overlappable region of the rightmost button to
290 // |kAlternateStyleSideInset| because the caption buttons are always
291 // at the right edge of the screen. (The left edge in RTL mode).
292 return kAlternateStyleSideInset -
293 AlternateFrameCaptionButton::GetXOverlap();
298 void FrameCaptionButtonContainerView::ButtonPressed(views::Button* sender,
299 const ui::Event& event) {
300 // When shift-clicking, slow down animations for visual debugging.
301 // We used to do this via an event filter that looked for the shift key being
302 // pressed but this interfered with several normal keyboard shortcuts.
303 scoped_ptr<ui::ScopedAnimationDurationScaleMode> slow_duration_mode;
304 if (event.IsShiftDown()) {
305 slow_duration_mode.reset(new ui::ScopedAnimationDurationScaleMode(
306 ui::ScopedAnimationDurationScaleMode::SLOW_DURATION));
309 ash::UserMetricsAction action =
310 ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MINIMIZE;
311 if (sender == minimize_button_) {
312 // The minimize button may move out from under the cursor.
313 ResetWindowControls();
315 } else if (sender == size_button_) {
316 // The size button may move out from under the cursor.
317 ResetWindowControls();
318 if (frame_->IsFullscreen()) { // Can be clicked in immersive fullscreen.
319 frame_->SetFullscreen(false);
320 action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_EXIT_FULLSCREEN;
321 } else if (frame_->IsMaximized()) {
323 action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_RESTORE;
326 action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MAXIMIZE;
328 } else if(sender == close_button_) {
330 action = ash::UMA_WINDOW_CLOSE_BUTTON_CLICK;
334 ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction(action);
337 void FrameCaptionButtonContainerView::SetButtonImages(
338 views::CustomButton* button,
341 int pushed_image_id) {
342 // When using the alternate button style, |button| does not inherit from
343 // views::ImageButton.
344 DCHECK(!switches::UseAlternateFrameCaptionButtonStyle());
345 views::ImageButton* image_button = static_cast<views::ImageButton*>(button);
346 ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance();
347 image_button->SetImage(views::CustomButton::STATE_NORMAL,
348 resource_bundle.GetImageSkiaNamed(normal_image_id));
349 image_button->SetImage(views::CustomButton::STATE_HOVERED,
350 resource_bundle.GetImageSkiaNamed(hot_image_id));
351 image_button->SetImage(views::CustomButton::STATE_PRESSED,
352 resource_bundle.GetImageSkiaNamed(pushed_image_id));