blob: 2c9e7d12122b391fb523ac66d4a343ff6b66675d [file] [log] [blame]
// Copyright 2018 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/public/cpp/frame_header.h"
#include "ash/public/cpp/caption_buttons/caption_button_model.h"
#include "ash/public/cpp/caption_buttons/frame_caption_button_container_view.h"
#include "ash/public/cpp/vector_icons/vector_icons.h"
#include "ash/public/cpp/window_properties.h"
#include "base/logging.h" // DCHECK
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/views/view.h"
#include "ui/views/widget/native_widget_aura.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/window/caption_button_layout_constants.h"
#include "ui/views/window/vector_icons/vector_icons.h"
namespace ash {
namespace {
// Duration of crossfade animation for activating and deactivating frame.
const int kActivationCrossfadeDurationMs = 200;
// Returns the available bounds for the header's title given the views to the
// left and right of the title, and the font used. |left_view| should be null
// if there is no view to the left of the title.
gfx::Rect GetAvailableTitleBounds(const views::View* left_view,
const views::View* right_view,
int header_height) {
// Space between the title text and the caption buttons.
constexpr int kTitleCaptionButtonSpacing = 5;
// Space between window icon and title text.
constexpr int kTitleIconOffsetX = 5;
// Space between window edge and title text, when there is no icon.
constexpr int kTitleNoIconOffsetX = 8;
const int x = left_view ? left_view->bounds().right() + kTitleIconOffsetX
: kTitleNoIconOffsetX;
const int title_height =
views::NativeWidgetAura::GetWindowTitleFontList().GetHeight();
DCHECK_LE(right_view->height(), header_height);
// We want to align the center points of the header and title vertically.
// Note that we can't just do (header_height - title_height) / 2, since this
// won't make the center points align perfectly vertically due to rounding.
// Floor when computing the center of |header_height| and when computing the
// center of the text.
const int header_center_y = header_height / 2;
const int title_center_y = title_height / 2;
const int y = std::max(0, header_center_y - title_center_y);
const int width =
std::max(0, right_view->x() - kTitleCaptionButtonSpacing - x);
return gfx::Rect(x, y, width, title_height);
}
// Returns true if the header for |widget| can animate to new visuals when the
// widget's activation changes. Returns false if the header should switch to
// new visuals instantaneously.
bool CanAnimateActivation(views::Widget* widget) {
// Do not animate the header if the parent (e.g.
// kShellWindowId_DefaultContainer) is already animating. All of the
// implementers of FrameHeader animate activation by continuously painting
// during the animation. This gives the parent's animation a slower frame
// rate.
// TODO(sky): Expose a better way to determine this rather than assuming the
// parent is a toplevel container.
aura::Window* window = widget->GetNativeWindow();
// TODO(sky): parent()->layer() is for mash until animations ported.
if (!window || !window->parent() || !window->parent()->layer())
return true;
ui::LayerAnimator* parent_layer_animator =
window->parent()->layer()->GetAnimator();
return !parent_layer_animator->IsAnimatingProperty(
ui::LayerAnimationElement::OPACITY) &&
!parent_layer_animator->IsAnimatingProperty(
ui::LayerAnimationElement::VISIBILITY);
}
} // namespace
///////////////////////////////////////////////////////////////////////////////
// FrameHeader, public:
FrameHeader::~FrameHeader() = default;
int FrameHeader::GetMinimumHeaderWidth() const {
// Ensure we have enough space for the window icon and buttons. We allow
// the title string to collapse to zero width.
return GetTitleBounds().x() +
caption_button_container_->GetMinimumSize().width();
}
void FrameHeader::PaintHeader(gfx::Canvas* canvas, Mode mode) {
Mode old_mode = mode_;
mode_ = mode;
if (mode_ != old_mode) {
UpdateCaptionButtonColors();
if (!initial_paint_ && CanAnimateActivation(target_widget_)) {
activation_animation_.SetSlideDuration(kActivationCrossfadeDurationMs);
if (mode_ == MODE_ACTIVE)
activation_animation_.Show();
else
activation_animation_.Hide();
} else {
if (mode_ == MODE_ACTIVE)
activation_animation_.Reset(1);
else
activation_animation_.Reset(0);
}
initial_paint_ = false;
}
DoPaintHeader(canvas);
}
void FrameHeader::LayoutHeader() {
LayoutHeaderInternal();
// Default to the header height; owning code may override via
// SetHeaderHeightForPainting().
painted_height_ = GetHeaderHeight();
}
int FrameHeader::GetHeaderHeight() const {
return caption_button_container_->height();
}
int FrameHeader::GetHeaderHeightForPainting() const {
return painted_height_;
}
void FrameHeader::SetHeaderHeightForPainting(int height) {
painted_height_ = height;
}
void FrameHeader::SchedulePaintForTitle() {
view_->SchedulePaintInRect(view_->GetMirroredRect(GetTitleBounds()));
}
void FrameHeader::SetPaintAsActive(bool paint_as_active) {
caption_button_container_->SetPaintAsActive(paint_as_active);
if (back_button_)
back_button_->set_paint_as_active(paint_as_active);
UpdateCaptionButtonColors();
}
void FrameHeader::OnShowStateChanged(ui::WindowShowState show_state) {
if (show_state == ui::SHOW_STATE_MINIMIZED)
return;
LayoutHeaderInternal();
}
void FrameHeader::SetLeftHeaderView(views::View* left_header_view) {
left_header_view_ = left_header_view;
}
void FrameHeader::SetBackButton(views::FrameCaptionButton* back_button) {
back_button_ = back_button;
if (back_button_) {
back_button_->SetBackgroundColor(GetCurrentFrameColor());
back_button_->SetImage(views::CAPTION_BUTTON_ICON_BACK,
views::FrameCaptionButton::ANIMATE_NO,
kWindowControlBackIcon);
}
}
views::FrameCaptionButton* FrameHeader::GetBackButton() const {
return back_button_;
}
void FrameHeader::SetFrameTextOverride(
const base::string16& frame_text_override) {
frame_text_override_ = frame_text_override;
SchedulePaintForTitle();
}
///////////////////////////////////////////////////////////////////////////////
// gfx::AnimationDelegate overrides:
void FrameHeader::AnimationProgressed(const gfx::Animation* animation) {
view_->SchedulePaintInRect(GetPaintedBounds());
}
///////////////////////////////////////////////////////////////////////////////
// FrameHeader, protected:
FrameHeader::FrameHeader(views::Widget* target_widget, views::View* view)
: target_widget_(target_widget), view_(view) {
DCHECK(target_widget);
DCHECK(view);
}
gfx::Rect FrameHeader::GetPaintedBounds() const {
return gfx::Rect(view_->width(), painted_height_);
}
void FrameHeader::UpdateCaptionButtonColors() {
const SkColor frame_color = GetCurrentFrameColor();
caption_button_container_->SetBackgroundColor(frame_color);
if (back_button_)
back_button_->SetBackgroundColor(frame_color);
}
void FrameHeader::PaintTitleBar(gfx::Canvas* canvas) {
base::string16 text = frame_text_override_;
views::WidgetDelegate* target_widget_delegate =
target_widget_->widget_delegate();
if (text.empty() && target_widget_delegate &&
target_widget_delegate->ShouldShowWindowTitle()) {
text = target_widget_delegate->GetWindowTitle();
}
if (!text.empty()) {
canvas->DrawStringRectWithFlags(
text, views::NativeWidgetAura::GetWindowTitleFontList(),
GetTitleColor(), view_->GetMirroredRect(GetTitleBounds()),
gfx::Canvas::NO_SUBPIXEL_RENDERING);
}
}
void FrameHeader::SetCaptionButtonContainer(
FrameCaptionButtonContainerView* caption_button_container) {
caption_button_container_ = caption_button_container;
caption_button_container_->SetButtonImage(views::CAPTION_BUTTON_ICON_MINIMIZE,
views::kWindowControlMinimizeIcon);
caption_button_container_->SetButtonImage(views::CAPTION_BUTTON_ICON_MENU,
kWindowControlMenuIcon);
caption_button_container_->SetButtonImage(views::CAPTION_BUTTON_ICON_CLOSE,
views::kWindowControlCloseIcon);
caption_button_container_->SetButtonImage(
views::CAPTION_BUTTON_ICON_LEFT_SNAPPED, kWindowControlLeftSnappedIcon);
caption_button_container_->SetButtonImage(
views::CAPTION_BUTTON_ICON_RIGHT_SNAPPED, kWindowControlRightSnappedIcon);
}
///////////////////////////////////////////////////////////////////////////////
// FrameHeader, private:
void FrameHeader::LayoutHeaderInternal() {
bool use_zoom_icons = caption_button_container()->model()->InZoomMode();
const gfx::VectorIcon& restore_icon = use_zoom_icons
? kWindowControlDezoomIcon
: views::kWindowControlRestoreIcon;
const gfx::VectorIcon& maximize_icon =
use_zoom_icons ? kWindowControlZoomIcon
: views::kWindowControlMaximizeIcon;
const gfx::VectorIcon& icon =
target_widget_->IsMaximized() || target_widget_->IsFullscreen()
? restore_icon
: maximize_icon;
caption_button_container()->SetButtonImage(
views::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE, icon);
caption_button_container()->SetButtonSize(
views::GetCaptionButtonLayoutSize(GetButtonLayoutSize()));
const gfx::Size caption_button_container_size =
caption_button_container()->GetPreferredSize();
caption_button_container()->SetBounds(
view_->width() - caption_button_container_size.width(), 0,
caption_button_container_size.width(),
caption_button_container_size.height());
caption_button_container()->Layout();
int origin = 0;
if (back_button_) {
gfx::Size size = back_button_->GetPreferredSize();
back_button_->SetBounds(0, 0, size.width(),
caption_button_container_size.height());
origin = back_button_->bounds().right();
}
if (left_header_view_) {
// Vertically center the left header view (typically the window icon) with
// respect to the caption button container.
const gfx::Size icon_size(left_header_view_->GetPreferredSize());
const int icon_offset_y = (GetHeaderHeight() - icon_size.height()) / 2;
constexpr int kLeftViewXInset = 9;
left_header_view_->SetBounds(kLeftViewXInset + origin, icon_offset_y,
icon_size.width(), icon_size.height());
}
}
gfx::Rect FrameHeader::GetTitleBounds() const {
views::View* left_view = left_header_view_ ? left_header_view_ : back_button_;
return GetAvailableTitleBounds(left_view, caption_button_container_,
GetHeaderHeight());
}
} // namespace ash