blob: 5c024a0e8e347949f926451329813c7508f89d92 [file] [log] [blame]
// Copyright 2013 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 "ash/wm/caption_buttons/frame_caption_button_container_view.h"
#include "ash/ash_switches.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "ash/wm/caption_buttons/alternate_frame_caption_button.h"
#include "ash/wm/caption_buttons/frame_maximize_button.h"
#include "ash/wm/window_state.h"
#include "grit/ash_resources.h"
#include "grit/ui_strings.h" // Accessibility names
#include "ui/base/hit_test.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/gfx/canvas.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
namespace ash {
namespace {
// Constants for normal button style -------------------------------------------
// The distance between buttons. AlternateFrameCaptionButton::GetXOverlap() is
// used to compute the distance between buttons for the alternate button style.
const int kDistanceBetweenButtons = -1;
// Constants for alternate button style ----------------------------------------
// Spacings between the buttons and the view's top and bottom borders (if any).
const int kAlternateStyleShortHeaderTopInset = 0;
const int kAlternateStyleTallHeaderTopInset = 2;
const int kAlternateStyleShortHeaderBottomInset = 0;
const int kAlternateStyleTallHeaderBottomInset = 1;
// Ideal spacing between:
// - Right edge of the leftmost button's overlappable region and the view's left
// edge.
// - Left edge of the rightmost button's overlappable region and the view's
// right edge.
// Used in GetLeftInset() and GetRightInset().
const int kAlternateStyleSideInset = 5;
// Converts |point| from |src| to |dst| and hittests against |dst|.
bool ConvertPointToViewAndHitTest(const views::View* src,
const views::View* dst,
const gfx::Point& point) {
gfx::Point converted(point);
views::View::ConvertPointToTarget(src, dst, &converted);
return dst->HitTestPoint(converted);
}
} // namespace
// static
const char FrameCaptionButtonContainerView::kViewClassName[] =
"FrameCaptionButtonContainerView";
FrameCaptionButtonContainerView::FrameCaptionButtonContainerView(
views::Widget* frame,
MinimizeAllowed minimize_allowed)
: frame_(frame),
header_style_(HEADER_STYLE_SHORT),
minimize_button_(NULL),
size_button_(NULL),
close_button_(NULL) {
bool alternate_style = switches::UseAlternateFrameCaptionButtonStyle();
// Insert the buttons left to right.
if (alternate_style) {
minimize_button_ = new AlternateFrameCaptionButton(this,
AlternateFrameCaptionButton::ACTION_MINIMIZE);
size_button_ = new AlternateFrameCaptionButton(this,
AlternateFrameCaptionButton::ACTION_MAXIMIZE_RESTORE);
close_button_ = new AlternateFrameCaptionButton(this,
AlternateFrameCaptionButton::ACTION_CLOSE);
} else {
minimize_button_ = new views::ImageButton(this);
size_button_ = new FrameMaximizeButton(this, frame);
close_button_ = new views::ImageButton(this);
}
minimize_button_->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE));
// Hide |minimize_button_| when using the non-alternate button style because
// |size_button_| is capable of minimizing in this case.
// TODO(pkotwicz): We should probably show the minimize button when in
// "always maximized" mode.
minimize_button_->SetVisible(
minimize_allowed == MINIMIZE_ALLOWED &&
(alternate_style || !frame_->widget_delegate()->CanMaximize()));
AddChildView(minimize_button_);
size_button_->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE));
size_button_->SetVisible(frame_->widget_delegate()->CanMaximize() &&
!ash::Shell::IsForcedMaximizeMode());
AddChildView(size_button_);
close_button_->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
AddChildView(close_button_);
button_separator_ = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
IDR_AURA_WINDOW_BUTTON_SEPARATOR).AsImageSkia();
}
FrameCaptionButtonContainerView::~FrameCaptionButtonContainerView() {
}
void FrameCaptionButtonContainerView::ResetWindowControls() {
minimize_button_->SetState(views::CustomButton::STATE_NORMAL);
size_button_->SetState(views::CustomButton::STATE_NORMAL);
}
int FrameCaptionButtonContainerView::NonClientHitTest(
const gfx::Point& point) const {
if (close_button_->visible() &&
ConvertPointToViewAndHitTest(this, close_button_, point)) {
return HTCLOSE;
} else if (size_button_->visible() &&
ConvertPointToViewAndHitTest(this, size_button_, point)) {
return HTMAXBUTTON;
} else if (minimize_button_->visible() &&
ConvertPointToViewAndHitTest(this, minimize_button_, point)) {
return HTMINBUTTON;
}
return HTNOWHERE;
}
gfx::Size FrameCaptionButtonContainerView::GetPreferredSize() {
int button_separation = GetDistanceBetweenButtons();
int width = 0;
bool first_visible = true;
for (int i = 0; i < child_count(); ++i) {
views::View* child = child_at(i);
if (!child->visible())
continue;
width += child_at(i)->GetPreferredSize().width();
if (!first_visible)
width += button_separation;
first_visible = false;
}
gfx::Insets insets(GetInsets());
return gfx::Size(
width + insets.width() + GetLeftInset() + GetRightInset(),
close_button_->GetPreferredSize().height() + insets.height());
}
void FrameCaptionButtonContainerView::Layout() {
if (switches::UseAlternateFrameCaptionButtonStyle()) {
int top_inset = kAlternateStyleShortHeaderTopInset;
int bottom_inset = kAlternateStyleShortHeaderBottomInset;
if (header_style_ == HEADER_STYLE_TALL) {
top_inset = kAlternateStyleTallHeaderTopInset;
bottom_inset = kAlternateStyleTallHeaderBottomInset;
}
minimize_button_->set_border(
views::Border::CreateEmptyBorder(top_inset, 0, bottom_inset, 0));
size_button_->set_border(
views::Border::CreateEmptyBorder(top_inset, 0, bottom_inset, 0));
close_button_->set_border(
views::Border::CreateEmptyBorder(top_inset, 0, bottom_inset, 0));
} else {
SetButtonImages(minimize_button_,
IDR_AURA_WINDOW_MINIMIZE_SHORT,
IDR_AURA_WINDOW_MINIMIZE_SHORT_H,
IDR_AURA_WINDOW_MINIMIZE_SHORT_P);
if (header_style_ == HEADER_STYLE_MAXIMIZED_HOSTED_APP) {
SetButtonImages(size_button_,
IDR_AURA_WINDOW_FULLSCREEN_RESTORE,
IDR_AURA_WINDOW_FULLSCREEN_RESTORE_H,
IDR_AURA_WINDOW_FULLSCREEN_RESTORE_P);
SetButtonImages(close_button_,
IDR_AURA_WINDOW_FULLSCREEN_CLOSE,
IDR_AURA_WINDOW_FULLSCREEN_CLOSE_H,
IDR_AURA_WINDOW_FULLSCREEN_CLOSE_P);
} else if (header_style_ == HEADER_STYLE_SHORT) {
// The new assets only make sense if the window is maximized or fullscreen
// because we usually use a black header in this case.
if ((frame_->IsMaximized() || frame_->IsFullscreen()) &&
wm::GetWindowState(
frame_->GetNativeWindow())->tracked_by_workspace()) {
SetButtonImages(size_button_,
IDR_AURA_WINDOW_MAXIMIZED_RESTORE2,
IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_H,
IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_P);
SetButtonImages(close_button_,
IDR_AURA_WINDOW_MAXIMIZED_CLOSE2,
IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_H,
IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_P);
} else {
SetButtonImages(size_button_,
IDR_AURA_WINDOW_MAXIMIZED_RESTORE,
IDR_AURA_WINDOW_MAXIMIZED_RESTORE_H,
IDR_AURA_WINDOW_MAXIMIZED_RESTORE_P);
SetButtonImages(close_button_,
IDR_AURA_WINDOW_MAXIMIZED_CLOSE,
IDR_AURA_WINDOW_MAXIMIZED_CLOSE_H,
IDR_AURA_WINDOW_MAXIMIZED_CLOSE_P);
}
} else {
SetButtonImages(size_button_,
IDR_AURA_WINDOW_MAXIMIZE,
IDR_AURA_WINDOW_MAXIMIZE_H,
IDR_AURA_WINDOW_MAXIMIZE_P);
SetButtonImages(close_button_,
IDR_AURA_WINDOW_CLOSE,
IDR_AURA_WINDOW_CLOSE_H,
IDR_AURA_WINDOW_CLOSE_P);
}
}
gfx::Insets insets(GetInsets());
int x = insets.left() + GetLeftInset();
int y_inset = insets.top();
int button_separation = GetDistanceBetweenButtons();
for (int i = 0; i < child_count(); ++i) {
views::View* child = child_at(i);
if (!child->visible())
continue;
gfx::Size size = child->GetPreferredSize();
child->SetBounds(x, y_inset, size.width(), size.height());
// Do not allow |child| to paint over the left border.
child->set_clip_insets(
gfx::Insets(0, std::max(0, insets.left() - x), 0, 0));
x += size.width() + button_separation;
}
}
const char* FrameCaptionButtonContainerView::GetClassName() const {
return kViewClassName;
}
void FrameCaptionButtonContainerView::OnPaint(gfx::Canvas* canvas) {
views::View::OnPaint(canvas);
// The alternate button style and AppNonClientFrameViewAsh do not paint the
// button separator.
if (header_style_ != HEADER_STYLE_MAXIMIZED_HOSTED_APP &&
!switches::UseAlternateFrameCaptionButtonStyle()) {
// We should have at most two visible buttons. The button separator is
// always painted underneath the close button regardless of whether a
// button other than the close button is visible.
gfx::Rect divider(close_button_->bounds().origin(),
button_separator_.size());
canvas->DrawImageInt(button_separator_,
GetMirroredXForRect(divider),
divider.y());
}
}
int FrameCaptionButtonContainerView::GetDistanceBetweenButtons() const {
if (switches::UseAlternateFrameCaptionButtonStyle())
return AlternateFrameCaptionButton::GetXOverlap() * -2;
return kDistanceBetweenButtons;
}
int FrameCaptionButtonContainerView::GetLeftInset() const {
if (switches::UseAlternateFrameCaptionButtonStyle()) {
// If using the alternate button style and there is a border, clip the
// left overlappable region of the leftmost button to
// |kAlternateStyleSideInset|.
// Otherwise, allow enough room for the entire left overlappable region of
// the leftmost button to fit in the view.
if (border() && border()->GetInsets().left()) {
return kAlternateStyleSideInset -
AlternateFrameCaptionButton::GetXOverlap();
}
}
return 0;
}
int FrameCaptionButtonContainerView::GetRightInset() const {
if (switches::UseAlternateFrameCaptionButtonStyle()) {
// Always clip the right overlappable region of the rightmost button to
// |kAlternateStyleSideInset| because the caption buttons are always
// at the right edge of the screen. (The left edge in RTL mode).
return kAlternateStyleSideInset -
AlternateFrameCaptionButton::GetXOverlap();
}
return 0;
}
void FrameCaptionButtonContainerView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
// When shift-clicking, slow down animations for visual debugging.
// We used to do this via an event filter that looked for the shift key being
// pressed but this interfered with several normal keyboard shortcuts.
scoped_ptr<ui::ScopedAnimationDurationScaleMode> slow_duration_mode;
if (event.IsShiftDown()) {
slow_duration_mode.reset(new ui::ScopedAnimationDurationScaleMode(
ui::ScopedAnimationDurationScaleMode::SLOW_DURATION));
}
ash::UserMetricsAction action =
ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MINIMIZE;
if (sender == minimize_button_) {
// The minimize button may move out from under the cursor.
ResetWindowControls();
frame_->Minimize();
} else if (sender == size_button_) {
// The size button may move out from under the cursor.
ResetWindowControls();
if (frame_->IsFullscreen()) { // Can be clicked in immersive fullscreen.
frame_->SetFullscreen(false);
action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_EXIT_FULLSCREEN;
} else if (frame_->IsMaximized()) {
frame_->Restore();
action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_RESTORE;
} else {
frame_->Maximize();
action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MAXIMIZE;
}
} else if(sender == close_button_) {
frame_->Close();
action = ash::UMA_WINDOW_CLOSE_BUTTON_CLICK;
} else {
return;
}
ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction(action);
}
void FrameCaptionButtonContainerView::SetButtonImages(
views::CustomButton* button,
int normal_image_id,
int hot_image_id,
int pushed_image_id) {
// When using the alternate button style, |button| does not inherit from
// views::ImageButton.
DCHECK(!switches::UseAlternateFrameCaptionButtonStyle());
views::ImageButton* image_button = static_cast<views::ImageButton*>(button);
ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance();
image_button->SetImage(views::CustomButton::STATE_NORMAL,
resource_bundle.GetImageSkiaNamed(normal_image_id));
image_button->SetImage(views::CustomButton::STATE_HOVERED,
resource_bundle.GetImageSkiaNamed(hot_image_id));
image_button->SetImage(views::CustomButton::STATE_PRESSED,
resource_bundle.GetImageSkiaNamed(pushed_image_id));
}
} // namespace ash