diff options
author | sky <sky@chromium.org> | 2015-11-18 18:42:05 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-11-19 02:42:46 +0000 |
commit | afeecbf9c6549bdfb0e00492d206f47a3088b40f (patch) | |
tree | c19dcf2c2d2ffe18da87be55b7febf2cccb55a9c /mash | |
parent | 42ef946ac829db450d131ad06a399a85f9fe207f (diff) | |
download | chromium_src-afeecbf9c6549bdfb0e00492d206f47a3088b40f.zip chromium_src-afeecbf9c6549bdfb0e00492d206f47a3088b40f.tar.gz chromium_src-afeecbf9c6549bdfb0e00492d206f47a3088b40f.tar.bz2 |
Gets mustash frames looking like that of ash
This is basically a fork of various portions of ash frame code. I left
parts we have no support for yet out, but kept some things that we'll
need to support eventually.
BUG=548426
TEST=none
R=ben@chromium.org
Review URL: https://codereview.chromium.org/1459653002
Cr-Commit-Position: refs/heads/master@{#360501}
Diffstat (limited to 'mash')
25 files changed, 2147 insertions, 179 deletions
diff --git a/mash/wm/BUILD.gn b/mash/wm/BUILD.gn index 373ef7a..fc8d2e86 100644 --- a/mash/wm/BUILD.gn +++ b/mash/wm/BUILD.gn @@ -5,6 +5,7 @@ import("//build/config/ui.gni") import("//mojo/public/mojo_application.gni") import("//mojo/public/tools/bindings/mojom.gni") +import("//tools/grit/repack.gni") group("wm") { testonly = true @@ -18,14 +19,26 @@ source_set("example_wm_lib") { sources = [ "background_layout.cc", "background_layout.h", + "frame/caption_buttons/caption_button_types.h", + "frame/caption_buttons/frame_caption_button.cc", + "frame/caption_buttons/frame_caption_button.h", + "frame/caption_buttons/frame_caption_button_container_view.cc", + "frame/caption_buttons/frame_caption_button_container_view.h", + "frame/default_header_painter.cc", + "frame/default_header_painter.h", + "frame/frame_border_hit_test_controller.cc", + "frame/frame_border_hit_test_controller.h", + "frame/header_painter.h", + "frame/header_painter_util.cc", + "frame/header_painter_util.h", + "frame/move_loop.cc", + "frame/move_loop.h", + "frame/non_client_frame_view_mash.cc", + "frame/non_client_frame_view_mash.h", "layout_manager.cc", "layout_manager.h", - "move_loop.cc", - "move_loop.h", "non_client_frame_controller.cc", "non_client_frame_controller.h", - "non_client_frame_view_impl.cc", - "non_client_frame_view_impl.h", "property_util.cc", "property_util.h", "shelf_layout.cc", @@ -44,13 +57,19 @@ source_set("example_wm_lib") { "//components/mus/public/cpp", "//components/mus/public/interfaces", "//mash/wm/public/interfaces", + "//mash/wm/resources", "//mojo/application/public/cpp", "//mojo/common:common_base", "//mojo/converters/geometry", "//mojo/converters/input_events", "//mojo/services/tracing/public/cpp", "//skia", + "//ui/aura", + "//ui/events", + "//ui/gfx", + "//ui/gfx/geometry", "//ui/mojo/init", + "//ui/strings", "//ui/views", "//ui/views/mus:for_mojo_application", ] @@ -63,15 +82,33 @@ mojo_native_application("example_wm") { deps = [ ":example_wm_lib", + ":resources", "//mojo/application/public/cpp", - "//ui/views/mus:resources", ] data_deps = [ "//components/mus", ] - resources = [ "$root_out_dir/views_mus_resources.pak" ] + resources = [ "$root_out_dir/mash_wm_resources.pak" ] +} + +repack("resources") { + sources = [ + "$root_gen_dir/mash/wm/resources/mash_wm_resources_100_percent.pak", + "$root_gen_dir/ui/resources/ui_resources_100_percent.pak", + "$root_gen_dir/ui/strings/app_locale_settings_en-US.pak", + "$root_gen_dir/ui/strings/ui_strings_en-US.pak", + "$root_gen_dir/ui/views/resources/views_resources_100_percent.pak", + ] + output = "$root_out_dir/mash_wm_resources.pak" + deps = [ + "//mash/wm/resources", + "//ui/resources", + "//ui/strings", + "//ui/views/mus:resources", + "//ui/views/resources", + ] } mojo_native_application("apptests") { @@ -105,7 +142,7 @@ source_set("unittests") { testonly = true sources = [ - "move_loop_unittest.cc", + "frame/move_loop_unittest.cc", ] deps = [ diff --git a/mash/wm/DEPS b/mash/wm/DEPS new file mode 100644 index 0000000..810a46a --- /dev/null +++ b/mash/wm/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+grit/mash_wm_resources.h", +] diff --git a/mash/wm/frame/caption_buttons/caption_button_types.h b/mash/wm/frame/caption_buttons/caption_button_types.h new file mode 100644 index 0000000..3bda201 --- /dev/null +++ b/mash/wm/frame/caption_buttons/caption_button_types.h @@ -0,0 +1,27 @@ +// Copyright 2015 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. + +#ifndef MASH_WM_FRAME_CAPTION_BUTTONS_CAPTION_BUTTON_TYPES_H_ +#define MASH_WM_FRAME_CAPTION_BUTTONS_CAPTION_BUTTON_TYPES_H_ + +namespace mash { +namespace wm { + +// These are the icon types that a caption button can have. The size button's +// action (SnapType) can be different from its icon. +enum CaptionButtonIcon { + CAPTION_BUTTON_ICON_MINIMIZE, + CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE, + CAPTION_BUTTON_ICON_CLOSE, + CAPTION_BUTTON_ICON_LEFT_SNAPPED, + CAPTION_BUTTON_ICON_RIGHT_SNAPPED, + CAPTION_BUTTON_ICON_BACK, + CAPTION_BUTTON_ICON_LOCATION, + CAPTION_BUTTON_ICON_COUNT +}; + +} // namespace wm +} // namespace mash + +#endif // MASH_WM_FRAME_CAPTION_BUTTONS_CAPTION_BUTTON_TYPES_H_ diff --git a/mash/wm/frame/caption_buttons/frame_caption_button.cc b/mash/wm/frame/caption_buttons/frame_caption_button.cc new file mode 100644 index 0000000..ca1501f --- /dev/null +++ b/mash/wm/frame/caption_buttons/frame_caption_button.cc @@ -0,0 +1,195 @@ +// Copyright 2015 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 "mash/wm/frame/caption_buttons/frame_caption_button.h" + +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/animation/slide_animation.h" +#include "ui/gfx/animation/throb_animation.h" +#include "ui/gfx/canvas.h" + +namespace mash { +namespace wm { + +namespace { + +// The duration of the crossfade animation when swapping the button's images. +const int kSwapImagesAnimationDurationMs = 200; + +// The duration of the fade out animation of the old icon during a crossfade +// animation as a ratio of |kSwapImagesAnimationDurationMs|. +const float kFadeOutRatio = 0.5f; + +// The alpha to draw inactive icons with. +const float kInactiveIconAlpha = 0.2f; + +} // namespace + +// static +const char FrameCaptionButton::kViewClassName[] = "FrameCaptionButton"; + +FrameCaptionButton::FrameCaptionButton(views::ButtonListener* listener, + CaptionButtonIcon icon) + : CustomButton(listener), + icon_(icon), + paint_as_active_(false), + alpha_(255), + icon_image_id_(-1), + hovered_background_image_id_(-1), + pressed_background_image_id_(-1), + swap_images_animation_(new gfx::SlideAnimation(this)) { + swap_images_animation_->Reset(1); + + // Do not flip the gfx::Canvas passed to the OnPaint() method. The snap left + // and snap right button icons should not be flipped. The other icons are + // horizontally symmetrical. +} + +FrameCaptionButton::~FrameCaptionButton() {} + +void FrameCaptionButton::SetImages(CaptionButtonIcon icon, + Animate animate, + int icon_image_id, + int hovered_background_image_id, + int pressed_background_image_id) { + // The early return is dependant on |animate| because callers use SetImages() + // with ANIMATE_NO to progress the crossfade animation to the end. + if (icon == icon_ && + (animate == ANIMATE_YES || !swap_images_animation_->is_animating()) && + icon_image_id == icon_image_id_ && + hovered_background_image_id == hovered_background_image_id_ && + pressed_background_image_id == pressed_background_image_id_) { + return; + } + + if (animate == ANIMATE_YES) + crossfade_icon_image_ = icon_image_; + + icon_ = icon; + icon_image_id_ = icon_image_id; + // TODO(sky): it doesn't seem like these are used. + hovered_background_image_id_ = hovered_background_image_id; + pressed_background_image_id_ = pressed_background_image_id; + + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + icon_image_ = *rb.GetImageSkiaNamed(icon_image_id); + hovered_background_image_ = + *rb.GetImageSkiaNamed(hovered_background_image_id); + pressed_background_image_ = + *rb.GetImageSkiaNamed(pressed_background_image_id); + + if (animate == ANIMATE_YES) { + swap_images_animation_->Reset(0); + swap_images_animation_->SetSlideDuration(kSwapImagesAnimationDurationMs); + swap_images_animation_->Show(); + } else { + swap_images_animation_->Reset(1); + } + PreferredSizeChanged(); + SchedulePaint(); +} + +bool FrameCaptionButton::IsAnimatingImageSwap() const { + return swap_images_animation_->is_animating(); +} + +void FrameCaptionButton::SetAlpha(int alpha) { + if (alpha_ != alpha) { + alpha_ = alpha; + SchedulePaint(); + } +} + +gfx::Size FrameCaptionButton::GetPreferredSize() const { + return hovered_background_image_.isNull() ? gfx::Size() + : hovered_background_image_.size(); +} + +const char* FrameCaptionButton::GetClassName() const { + return kViewClassName; +} + +void FrameCaptionButton::OnPaint(gfx::Canvas* canvas) { + if (hover_animation_->is_animating() || state() == STATE_HOVERED) { + int hovered_background_alpha = + hover_animation_->is_animating() + ? hover_animation_->CurrentValueBetween(0, 255) + : 255; + SkPaint paint; + paint.setAlpha(hovered_background_alpha); + canvas->DrawImageInt(hovered_background_image_, 0, 0, paint); + } else if (state() == STATE_PRESSED) { + canvas->DrawImageInt(pressed_background_image_, 0, 0); + } + + int icon_alpha = swap_images_animation_->CurrentValueBetween(0, 255); + int crossfade_icon_alpha = 0; + if (icon_alpha < static_cast<int>(kFadeOutRatio * 255)) + crossfade_icon_alpha = static_cast<int>(255 - icon_alpha / kFadeOutRatio); + + if (crossfade_icon_alpha > 0 && !crossfade_icon_image_.isNull()) { + gfx::Canvas icon_canvas(icon_image_.size(), canvas->image_scale(), false); + SkPaint paint; + paint.setAlpha(icon_alpha); + icon_canvas.DrawImageInt(icon_image_, 0, 0, paint); + + paint.setAlpha(crossfade_icon_alpha); + paint.setXfermodeMode(SkXfermode::kPlus_Mode); + icon_canvas.DrawImageInt(crossfade_icon_image_, 0, 0, paint); + + PaintCentered(canvas, gfx::ImageSkia(icon_canvas.ExtractImageRep()), + alpha_); + } else { + if (!swap_images_animation_->is_animating()) + icon_alpha = alpha_; + PaintCentered(canvas, icon_image_, icon_alpha); + } +} + +void FrameCaptionButton::OnGestureEvent(ui::GestureEvent* event) { + // CustomButton does not become pressed when the user drags off and then back + // onto the button. Make FrameCaptionButton pressed in this case because this + // behavior is more consistent with AlternateFrameSizeButton. + if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN || + event->type() == ui::ET_GESTURE_SCROLL_UPDATE) { + if (HitTestPoint(event->location())) { + SetState(STATE_PRESSED); + RequestFocus(); + event->StopPropagation(); + } else { + SetState(STATE_NORMAL); + } + } else if (event->type() == ui::ET_GESTURE_SCROLL_END) { + if (HitTestPoint(event->location())) { + SetState(STATE_HOVERED); + NotifyClick(*event); + event->StopPropagation(); + } + } + CustomButton::OnGestureEvent(event); +} + +void FrameCaptionButton::PaintCentered(gfx::Canvas* canvas, + const gfx::ImageSkia& to_center, + int alpha) { + if (!paint_as_active_) { + // Paint icons as active when they are hovered over or pressed. + double inactive_alpha = kInactiveIconAlpha; + if (hover_animation_->is_animating()) { + inactive_alpha = + hover_animation_->CurrentValueBetween(inactive_alpha, 1.0f); + } else if (state() == STATE_PRESSED || state() == STATE_HOVERED) { + inactive_alpha = 1.0f; + } + alpha *= inactive_alpha; + } + + SkPaint paint; + paint.setAlpha(alpha); + canvas->DrawImageInt(to_center, (width() - to_center.width()) / 2, + (height() - to_center.height()) / 2, paint); +} + +} // namespace wm +} // namespace mash diff --git a/mash/wm/frame/caption_buttons/frame_caption_button.h b/mash/wm/frame/caption_buttons/frame_caption_button.h new file mode 100644 index 0000000..6708327 --- /dev/null +++ b/mash/wm/frame/caption_buttons/frame_caption_button.h @@ -0,0 +1,102 @@ +// Copyright 2015 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. + +#ifndef MASH_WM_FRAME_CAPTION_BUTTONS_FRAME_CAPTION_BUTTON_H_ +#define MASH_WM_FRAME_CAPTION_BUTTONS_FRAME_CAPTION_BUTTON_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "mash/wm/frame/caption_buttons/caption_button_types.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/views/controls/button/custom_button.h" + +namespace gfx { +class SlideAnimation; +} + +namespace mash { +namespace wm { + +// Base class for the window caption buttons (minimize, maximize, restore, +// close). +class FrameCaptionButton : public views::CustomButton { + public: + enum Animate { ANIMATE_YES, ANIMATE_NO }; + + static const char kViewClassName[]; + + FrameCaptionButton(views::ButtonListener* listener, CaptionButtonIcon icon); + ~FrameCaptionButton() override; + + // Sets the images to use to paint the button. If |animate| is ANIMATE_YES, + // the button crossfades to the new visuals. If the image ids match those + // currently used by the button and |animate| is ANIMATE_NO the crossfade + // animation is progressed to the end. + void SetImages(CaptionButtonIcon icon, + Animate animate, + int icon_image_id, + int hovered_background_image_id, + int pressed_background_image_id); + + // Returns true if the button is crossfading to new visuals set in + // SetImages(). + bool IsAnimatingImageSwap() const; + + // Sets the alpha to use for painting. Used to animate visibility changes. + void SetAlpha(int alpha); + + // views::View overrides: + gfx::Size GetPreferredSize() const override; + const char* GetClassName() const override; + void OnPaint(gfx::Canvas* canvas) override; + + void set_paint_as_active(bool paint_as_active) { + paint_as_active_ = paint_as_active; + } + + CaptionButtonIcon icon() const { return icon_; } + + int icon_image_id() const { return icon_image_id_; } + + protected: + // views::CustomButton override: + void OnGestureEvent(ui::GestureEvent* event) override; + + private: + // Paints |to_center| centered within the button with |alpha|. + void PaintCentered(gfx::Canvas* canvas, + const gfx::ImageSkia& to_center, + int alpha); + + // The button's current icon. + CaptionButtonIcon icon_; + + // Whether the button should be painted as active. + bool paint_as_active_; + + // Current alpha to use for painting. + int alpha_; + + // The images and image ids used to paint the button. + int icon_image_id_; + int hovered_background_image_id_; + int pressed_background_image_id_; + gfx::ImageSkia icon_image_; + gfx::ImageSkia hovered_background_image_; + gfx::ImageSkia pressed_background_image_; + + // The icon image to crossfade from. + gfx::ImageSkia crossfade_icon_image_; + + // Crossfade animation started when the button's images are changed by + // SetImages(). + scoped_ptr<gfx::SlideAnimation> swap_images_animation_; + + DISALLOW_COPY_AND_ASSIGN(FrameCaptionButton); +}; + +} // namespace wm +} // namespace mash + +#endif // MASH_WM_FRAME_CAPTION_BUTTONS_FRAME_CAPTION_BUTTON_H_ diff --git a/mash/wm/frame/caption_buttons/frame_caption_button_container_view.cc b/mash/wm/frame/caption_buttons/frame_caption_button_container_view.cc new file mode 100644 index 0000000..3bd443b --- /dev/null +++ b/mash/wm/frame/caption_buttons/frame_caption_button_container_view.cc @@ -0,0 +1,353 @@ +// Copyright 2015 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 "mash/wm/frame/caption_buttons/frame_caption_button_container_view.h" + +#include <cmath> +#include <map> + +#include "mash/wm/frame/caption_buttons/frame_caption_button.h" +#include "ui/base/hit_test.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/gfx/animation/slide_animation.h" +#include "ui/gfx/animation/tween.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/point.h" +#include "ui/strings/grit/ui_strings.h" // Accessibility names +#include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_delegate.h" + +namespace mash { +namespace wm { + +namespace { + +// Duration of the animation of the position of |minimize_button_|. +const int kPositionAnimationDurationMs = 500; + +// Duration of the animation of the alpha of |size_button_|. +const int kAlphaAnimationDurationMs = 250; + +// Delay during |maximize_mode_animation_| hide to wait before beginning to +// animate the position of |minimize_button_|. +const int kHidePositionDelayMs = 100; + +// Duration of |maximize_mode_animation_| hiding. +// Hiding size button 250 +// |------------------------| +// Delay 100 Slide minimize button 500 +// |---------|-------------------------------------------------| +const int kHideAnimationDurationMs = + kHidePositionDelayMs + kPositionAnimationDurationMs; + +// Delay during |maximize_mode_animation_| show to wait before beginning to +// animate the alpha of |size_button_|. +const int kShowAnimationAlphaDelayMs = 100; + +// Duration of |maximize_mode_animation_| showing. +// Slide minimize button 500 +// |-------------------------------------------------| +// Delay 100 Show size button 250 +// |---------|-----------------------| +const int kShowAnimationDurationMs = kPositionAnimationDurationMs; + +// Value of |maximize_mode_animation_| showing to begin animating alpha of +// |size_button_|. +float SizeButtonShowStartValue() { + return static_cast<float>(kShowAnimationAlphaDelayMs) / + kShowAnimationDurationMs; +} + +// Amount of |maximize_mode_animation_| showing to animate the alpha of +// |size_button_|. +float SizeButtonShowDuration() { + return static_cast<float>(kAlphaAnimationDurationMs) / + kShowAnimationDurationMs; +} + +// Amount of |maximize_mode_animation_| hiding to animate the alpha of +// |size_button_|. +float SizeButtonHideDuration() { + return static_cast<float>(kAlphaAnimationDurationMs) / + kHideAnimationDurationMs; +} + +// Value of |maximize_mode_animation_| hiding to begin animating the position of +// |minimize_button_|. +float HidePositionStartValue() { + return 1.0f - + static_cast<float>(kHidePositionDelayMs) / kHideAnimationDurationMs; +} + +// 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); +} + +// Bounds animation values to the range 0.0 - 1.0. Allows for mapping of offset +// animations to the expected range so that gfx::Tween::CalculateValue() can be +// used. +double CapAnimationValue(double value) { + return std::min(1.0, std::max(0.0, value)); +} + +} // namespace + +// static +const char FrameCaptionButtonContainerView::kViewClassName[] = + "FrameCaptionButtonContainerView"; + +FrameCaptionButtonContainerView::FrameCaptionButtonContainerView( + views::Widget* frame) + : frame_(frame), + minimize_button_(NULL), + size_button_(NULL), + close_button_(NULL) { + const bool size_button_visibility = ShouldSizeButtonBeVisible(); + maximize_mode_animation_.reset(new gfx::SlideAnimation(this)); + maximize_mode_animation_->SetTweenType(gfx::Tween::LINEAR); + + // Ensure animation tracks visibility of size button. + if (size_button_visibility) + maximize_mode_animation_->Reset(1.0f); + + // Insert the buttons left to right. + minimize_button_ = new FrameCaptionButton(this, CAPTION_BUTTON_ICON_MINIMIZE); + minimize_button_->SetAccessibleName( + l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE)); + minimize_button_->SetVisible(frame_->widget_delegate()->CanMinimize()); + AddChildView(minimize_button_); + + size_button_ = + new FrameCaptionButton(this, CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE); + size_button_->SetAccessibleName( + l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE)); + size_button_->SetVisible(size_button_visibility); + AddChildView(size_button_); + + close_button_ = new FrameCaptionButton(this, CAPTION_BUTTON_ICON_CLOSE); + close_button_->SetAccessibleName( + l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE)); + AddChildView(close_button_); +} + +FrameCaptionButtonContainerView::~FrameCaptionButtonContainerView() {} + +void FrameCaptionButtonContainerView::SetButtonImages( + CaptionButtonIcon icon, + int icon_image_id, + int hovered_background_image_id, + int pressed_background_image_id) { + button_icon_id_map_[icon] = ButtonIconIds( + icon_image_id, hovered_background_image_id, pressed_background_image_id); + FrameCaptionButton* buttons[] = {minimize_button_, size_button_, + close_button_}; + for (size_t i = 0; i < arraysize(buttons); ++i) { + if (buttons[i]->icon() == icon) { + buttons[i]->SetImages(icon, FrameCaptionButton::ANIMATE_NO, icon_image_id, + hovered_background_image_id, + pressed_background_image_id); + } + } +} + +void FrameCaptionButtonContainerView::SetPaintAsActive(bool paint_as_active) { + minimize_button_->set_paint_as_active(paint_as_active); + size_button_->set_paint_as_active(paint_as_active); + close_button_->set_paint_as_active(paint_as_active); +} + +void FrameCaptionButtonContainerView::ResetWindowControls() { + SetButtonsToNormal(ANIMATE_NO); +} + +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; +} + +void FrameCaptionButtonContainerView::UpdateSizeButtonVisibility() { + bool visible = ShouldSizeButtonBeVisible(); + if (visible) { + size_button_->SetVisible(true); + maximize_mode_animation_->SetSlideDuration(kShowAnimationDurationMs); + maximize_mode_animation_->Show(); + } else { + maximize_mode_animation_->SetSlideDuration(kHideAnimationDurationMs); + maximize_mode_animation_->Hide(); + } +} + +gfx::Size FrameCaptionButtonContainerView::GetPreferredSize() const { + int width = 0; + for (int i = 0; i < child_count(); ++i) { + const views::View* child = child_at(i); + if (child->visible()) + width += child_at(i)->GetPreferredSize().width(); + } + return gfx::Size(width, close_button_->GetPreferredSize().height()); +} + +void FrameCaptionButtonContainerView::Layout() { + int x = 0; + 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, 0, size.width(), size.height()); + x += size.width(); + } + if (maximize_mode_animation_->is_animating()) { + AnimationProgressed(maximize_mode_animation_.get()); + } +} + +const char* FrameCaptionButtonContainerView::GetClassName() const { + return kViewClassName; +} + +void FrameCaptionButtonContainerView::AnimationEnded( + const gfx::Animation* animation) { + // Ensure that position is calculated at least once. + AnimationProgressed(animation); + + double current_value = maximize_mode_animation_->GetCurrentValue(); + if (current_value == 0.0) { + size_button_->SetVisible(false); + PreferredSizeChanged(); + } +} + +void FrameCaptionButtonContainerView::AnimationProgressed( + const gfx::Animation* animation) { + double current_value = animation->GetCurrentValue(); + int size_alpha = 0; + int minimize_x = 0; + if (maximize_mode_animation_->IsShowing()) { + double scaled_value = + CapAnimationValue((current_value - SizeButtonShowStartValue()) / + SizeButtonShowDuration()); + double tweened_value_alpha = + gfx::Tween::CalculateValue(gfx::Tween::EASE_OUT, scaled_value); + size_alpha = gfx::Tween::LinearIntValueBetween(tweened_value_alpha, 0, 255); + + double tweened_value_slide = + gfx::Tween::CalculateValue(gfx::Tween::EASE_OUT, current_value); + minimize_x = gfx::Tween::LinearIntValueBetween(tweened_value_slide, + size_button_->x(), 0); + } else { + double scaled_value_alpha = + CapAnimationValue((1.0f - current_value) / SizeButtonHideDuration()); + double tweened_value_alpha = + gfx::Tween::CalculateValue(gfx::Tween::EASE_IN, scaled_value_alpha); + size_alpha = gfx::Tween::LinearIntValueBetween(tweened_value_alpha, 255, 0); + + double scaled_value_position = CapAnimationValue( + (HidePositionStartValue() - current_value) / HidePositionStartValue()); + double tweened_value_position = + gfx::Tween::CalculateValue(gfx::Tween::EASE_OUT, scaled_value_position); + minimize_x = gfx::Tween::LinearIntValueBetween(tweened_value_position, 0, + size_button_->x()); + } + size_button_->SetAlpha(size_alpha); + minimize_button_->SetX(minimize_x); +} + +void FrameCaptionButtonContainerView::SetButtonIcon(FrameCaptionButton* button, + CaptionButtonIcon icon, + Animate animate) { + // The early return is dependant on |animate| because callers use + // SetButtonIcon() with ANIMATE_NO to progress |button|'s crossfade animation + // to the end. + if (button->icon() == icon && + (animate == ANIMATE_YES || !button->IsAnimatingImageSwap())) { + return; + } + + FrameCaptionButton::Animate fcb_animate = + (animate == ANIMATE_YES) ? FrameCaptionButton::ANIMATE_YES + : FrameCaptionButton::ANIMATE_NO; + std::map<CaptionButtonIcon, ButtonIconIds>::const_iterator it = + button_icon_id_map_.find(icon); + if (it != button_icon_id_map_.end()) { + button->SetImages(icon, fcb_animate, it->second.icon_image_id, + it->second.hovered_background_image_id, + it->second.pressed_background_image_id); + } +} + +bool FrameCaptionButtonContainerView::ShouldSizeButtonBeVisible() const { + return frame_->widget_delegate()->CanMaximize(); +} + +void FrameCaptionButtonContainerView::SetButtonsToNormal(Animate animate) { + SetButtonIcons(CAPTION_BUTTON_ICON_MINIMIZE, CAPTION_BUTTON_ICON_CLOSE, + animate); + minimize_button_->SetState(views::Button::STATE_NORMAL); + size_button_->SetState(views::Button::STATE_NORMAL); + close_button_->SetState(views::Button::STATE_NORMAL); +} + +void FrameCaptionButtonContainerView::SetButtonIcons( + CaptionButtonIcon minimize_button_icon, + CaptionButtonIcon close_button_icon, + Animate animate) { + SetButtonIcon(minimize_button_, minimize_button_icon, animate); + SetButtonIcon(close_button_, close_button_icon, animate); +} + +void FrameCaptionButtonContainerView::ButtonPressed(views::Button* sender, + const ui::Event& event) { + // Abort any animations of the button icons. + SetButtonsToNormal(ANIMATE_NO); + + if (sender == minimize_button_) { + frame_->Minimize(); + } else if (sender == size_button_) { + if (frame_->IsFullscreen()) { // Can be clicked in immersive fullscreen. + frame_->Restore(); + } else if (frame_->IsMaximized()) { + frame_->Restore(); + } else { + frame_->Maximize(); + } + } else if (sender == close_button_) { + frame_->Close(); + } +} + +FrameCaptionButtonContainerView::ButtonIconIds::ButtonIconIds() + : icon_image_id(-1), + hovered_background_image_id(-1), + pressed_background_image_id(-1) {} + +FrameCaptionButtonContainerView::ButtonIconIds::ButtonIconIds( + int icon_id, + int hovered_background_id, + int pressed_background_id) + : icon_image_id(icon_id), + hovered_background_image_id(hovered_background_id), + pressed_background_image_id(pressed_background_id) {} + +FrameCaptionButtonContainerView::ButtonIconIds::~ButtonIconIds() {} + +} // namespace wm +} // namespace mash diff --git a/mash/wm/frame/caption_buttons/frame_caption_button_container_view.h b/mash/wm/frame/caption_buttons/frame_caption_button_container_view.h new file mode 100644 index 0000000..73ae6a8 --- /dev/null +++ b/mash/wm/frame/caption_buttons/frame_caption_button_container_view.h @@ -0,0 +1,135 @@ +// Copyright 2015 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. + +#ifndef MASH_WM_FRAME_CAPTION_BUTTONS_FRAME_CAPTION_BUTTON_CONTAINER_VIEW_H_ +#define MASH_WM_FRAME_CAPTION_BUTTONS_FRAME_CAPTION_BUTTON_CONTAINER_VIEW_H_ + +#include <map> + +#include "base/macros.h" +#include "mash/wm/frame/caption_buttons/caption_button_types.h" +#include "ui/gfx/animation/animation_delegate.h" +#include "ui/views/controls/button/button.h" +#include "ui/views/view.h" + +namespace gfx { +class SlideAnimation; +} + +namespace views { +class Widget; +} + +namespace mash { +namespace wm { + +class FrameCaptionButton; + +// Container view for the frame caption buttons. It performs the appropriate +// action when a caption button is clicked. +class FrameCaptionButtonContainerView : public views::View, + public views::ButtonListener, + public gfx::AnimationDelegate { + public: + enum Animate { ANIMATE_YES, ANIMATE_NO }; + + static const char kViewClassName[]; + + // |frame| is the views::Widget that the caption buttons act on. + explicit FrameCaptionButtonContainerView(views::Widget* frame); + ~FrameCaptionButtonContainerView() override; + + // Sets the resource ids of the images to paint the button for |icon|. The + // FrameCaptionButtonContainerView will keep track of the images to use for + // |icon| even if none of the buttons currently use |icon|. + void SetButtonImages(CaptionButtonIcon icon, + int icon_image_id, + int hovered_background_image_id, + int pressed_background_image_id); + + // Sets whether the buttons should be painted as active. Does not schedule + // a repaint. + void SetPaintAsActive(bool paint_as_active); + + // Tell the window controls to reset themselves to the normal state. + void ResetWindowControls(); + + // Determines the window HT* code for the caption button at |point|. Returns + // HTNOWHERE if |point| is not over any of the caption buttons. |point| must + // be in the coordinates of the FrameCaptionButtonContainerView. + int NonClientHitTest(const gfx::Point& point) const; + + // Updates the size button's visibility based on whether |frame_| can be + // maximized and if maximize mode is enabled. A parent view should relayout + // to reflect the change in visibility. + void UpdateSizeButtonVisibility(); + + // views::View: + gfx::Size GetPreferredSize() const override; + void Layout() override; + const char* GetClassName() const override; + + // Overridden from gfx::AnimationDelegate: + void AnimationEnded(const gfx::Animation* animation) override; + void AnimationProgressed(const gfx::Animation* animation) override; + + private: + friend class FrameCaptionButtonContainerViewTest; + + struct ButtonIconIds { + ButtonIconIds(); + ButtonIconIds(int icon_id, + int hovered_background_id, + int pressed_background_id); + ~ButtonIconIds(); + + int icon_image_id; + int hovered_background_image_id; + int pressed_background_image_id; + }; + + // Sets |button|'s icon to |icon|. If |animate| is ANIMATE_YES, the button + // will crossfade to the new icon. If |animate| is ANIMATE_NO and + // |icon| == |button|->icon(), the crossfade animation is progressed to the + // end. + void SetButtonIcon(FrameCaptionButton* button, + CaptionButtonIcon icon, + Animate animate); + + // Returns true if maximize mode is not enabled, and |frame_| widget delegate + // can be maximized. + bool ShouldSizeButtonBeVisible() const; + + void SetButtonsToNormal(Animate animate); + void SetButtonIcons(CaptionButtonIcon minimize_button_icon, + CaptionButtonIcon close_button_icon, + Animate animate); + + // views::ButtonListener: + void ButtonPressed(views::Button* sender, const ui::Event& event) override; + + // The widget that the buttons act on. + views::Widget* frame_; + + // The buttons. In the normal button style, at most one of |minimize_button_| + // and |size_button_| is visible. + FrameCaptionButton* minimize_button_; + FrameCaptionButton* size_button_; + FrameCaptionButton* close_button_; + + // Mapping of the images needed to paint a button for each of the values of + // CaptionButtonIcon. + std::map<CaptionButtonIcon, ButtonIconIds> button_icon_id_map_; + + // Animation that affects the position of |minimize_button_| and the + // visibility of |size_button_|. + scoped_ptr<gfx::SlideAnimation> maximize_mode_animation_; + + DISALLOW_COPY_AND_ASSIGN(FrameCaptionButtonContainerView); +}; + +} // namespace wm +} // namespace mash + +#endif // MASH_WM_FRAME_CAPTION_BUTTONS_FRAME_CAPTION_BUTTON_CONTAINER_VIEW_H_ diff --git a/mash/wm/frame/default_header_painter.cc b/mash/wm/frame/default_header_painter.cc new file mode 100644 index 0000000..755ad7f --- /dev/null +++ b/mash/wm/frame/default_header_painter.cc @@ -0,0 +1,352 @@ +// Copyright 2015 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 "mash/wm/frame/default_header_painter.h" + +#include "base/debug/leak_annotations.h" +#include "base/logging.h" +#include "grit/mash_wm_resources.h" +#include "mash/wm/frame/caption_buttons/frame_caption_button_container_view.h" +#include "mash/wm/frame/header_painter_util.h" +#include "third_party/skia/include/core/SkPaint.h" +#include "third_party/skia/include/core/SkPath.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/animation/slide_animation.h" +#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/image/image.h" +#include "ui/gfx/scoped_canvas.h" +#include "ui/gfx/skia_util.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" + +using views::Widget; + +namespace { + +// Color for the window title text. +const SkColor kTitleTextColor = SkColorSetRGB(40, 40, 40); +// Color of the active window header/content separator line. +const SkColor kHeaderContentSeparatorColor = SkColorSetRGB(150, 150, 152); +// Color of the inactive window header/content separator line. +const SkColor kHeaderContentSeparatorInactiveColor = + SkColorSetRGB(180, 180, 182); +// The default color of the frame. +const SkColor kDefaultFrameColor = SkColorSetRGB(242, 242, 242); +// Duration of crossfade animation for activating and deactivating frame. +const int kActivationCrossfadeDurationMs = 200; +// Luminance below which to use white caption buttons. +const int kMaxLuminanceForLightButtons = 125; + +// Tiles an image into an area, rounding the top corners. +void TileRoundRect(gfx::Canvas* canvas, + const SkPaint& paint, + const gfx::Rect& bounds, + int corner_radius) { + SkRect rect = gfx::RectToSkRect(bounds); + const SkScalar corner_radius_scalar = SkIntToScalar(corner_radius); + SkScalar radii[8] = {corner_radius_scalar, + corner_radius_scalar, // top-left + corner_radius_scalar, + corner_radius_scalar, // top-right + 0, + 0, // bottom-right + 0, + 0}; // bottom-left + SkPath path; + path.addRoundRect(rect, radii, SkPath::kCW_Direction); + canvas->DrawPath(path, paint); +} + +// Returns the FontList to use for the title. +const gfx::FontList& GetTitleFontList() { + static const gfx::FontList* title_font_list = + new gfx::FontList(views::NativeWidgetAura::GetWindowTitleFontList()); + ANNOTATE_LEAKING_OBJECT_PTR(title_font_list); + return *title_font_list; +} + +} // namespace + +namespace mash { +namespace wm { + +/////////////////////////////////////////////////////////////////////////////// +// DefaultHeaderPainter, public: + +DefaultHeaderPainter::DefaultHeaderPainter() + : frame_(NULL), + view_(NULL), + left_header_view_(NULL), + left_view_x_inset_(HeaderPainterUtil::GetDefaultLeftViewXInset()), + active_frame_color_(kDefaultFrameColor), + inactive_frame_color_(kDefaultFrameColor), + caption_button_container_(NULL), + painted_height_(0), + mode_(MODE_INACTIVE), + initial_paint_(true), + activation_animation_(new gfx::SlideAnimation(this)) {} + +DefaultHeaderPainter::~DefaultHeaderPainter() {} + +void DefaultHeaderPainter::Init( + views::Widget* frame, + views::View* header_view, + FrameCaptionButtonContainerView* caption_button_container) { + DCHECK(frame); + DCHECK(header_view); + DCHECK(caption_button_container); + frame_ = frame; + view_ = header_view; + caption_button_container_ = caption_button_container; + UpdateAllButtonImages(); +} + +int DefaultHeaderPainter::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 DefaultHeaderPainter::PaintHeader(gfx::Canvas* canvas, Mode mode) { + Mode old_mode = mode_; + mode_ = mode; + + if (mode_ != old_mode) { + UpdateAllButtonImages(); + if (!initial_paint_ && HeaderPainterUtil::CanAnimateActivation(frame_)) { + 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; + } + + int corner_radius = (frame_->IsMaximized() || frame_->IsFullscreen()) + ? 0 + : HeaderPainterUtil::GetTopCornerRadiusWhenRestored(); + + SkPaint paint; + int active_alpha = activation_animation_->CurrentValueBetween(0, 255); + paint.setColor(color_utils::AlphaBlend(active_frame_color_, + inactive_frame_color_, active_alpha)); + + TileRoundRect(canvas, paint, GetLocalBounds(), corner_radius); + + if (!frame_->IsMaximized() && !frame_->IsFullscreen() && + mode_ == MODE_INACTIVE && !UsesCustomFrameColors()) { + PaintHighlightForInactiveRestoredWindow(canvas); + } + if (frame_->widget_delegate() && + frame_->widget_delegate()->ShouldShowWindowTitle()) { + PaintTitleBar(canvas); + } + if (!UsesCustomFrameColors()) + PaintHeaderContentSeparator(canvas); +} + +void DefaultHeaderPainter::LayoutHeader() { + UpdateSizeButtonImages(ShouldUseLightImages()); + caption_button_container_->Layout(); + + 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()); + + LayoutLeftHeaderView(); + + // The header/content separator line overlays the caption buttons. + SetHeaderHeightForPainting(caption_button_container_->height()); +} + +int DefaultHeaderPainter::GetHeaderHeight() const { + return caption_button_container_->height(); +} + +int DefaultHeaderPainter::GetHeaderHeightForPainting() const { + return painted_height_; +} + +void DefaultHeaderPainter::SetHeaderHeightForPainting(int height) { + painted_height_ = height; +} + +void DefaultHeaderPainter::SchedulePaintForTitle() { + view_->SchedulePaintInRect(GetTitleBounds()); +} + +void DefaultHeaderPainter::UpdateLeftViewXInset(int left_view_x_inset) { + if (left_view_x_inset_ != left_view_x_inset) { + left_view_x_inset_ = left_view_x_inset; + LayoutLeftHeaderView(); + } +} + +void DefaultHeaderPainter::SetFrameColors(SkColor active_frame_color, + SkColor inactive_frame_color) { + active_frame_color_ = active_frame_color; + inactive_frame_color_ = inactive_frame_color; + UpdateAllButtonImages(); +} + +void DefaultHeaderPainter::UpdateLeftHeaderView(views::View* left_header_view) { + left_header_view_ = left_header_view; +} + +/////////////////////////////////////////////////////////////////////////////// +// gfx::AnimationDelegate overrides: + +void DefaultHeaderPainter::AnimationProgressed( + const gfx::Animation* animation) { + view_->SchedulePaintInRect(GetLocalBounds()); +} + +/////////////////////////////////////////////////////////////////////////////// +// DefaultHeaderPainter, private: + +void DefaultHeaderPainter::PaintHighlightForInactiveRestoredWindow( + gfx::Canvas* canvas) { + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + gfx::ImageSkia top_edge = + *rb.GetImageSkiaNamed(IDR_MASH_WM_WINDOW_HEADER_SHADE_INACTIVE_TOP); + gfx::ImageSkia left_edge = + *rb.GetImageSkiaNamed(IDR_MASH_WM_WINDOW_HEADER_SHADE_INACTIVE_LEFT); + gfx::ImageSkia right_edge = + *rb.GetImageSkiaNamed(IDR_MASH_WM_WINDOW_HEADER_SHADE_INACTIVE_RIGHT); + gfx::ImageSkia bottom_edge = + *rb.GetImageSkiaNamed(IDR_MASH_WM_WINDOW_HEADER_SHADE_INACTIVE_BOTTOM); + + int left_edge_width = left_edge.width(); + int right_edge_width = right_edge.width(); + canvas->DrawImageInt(left_edge, 0, 0); + canvas->DrawImageInt(right_edge, view_->width() - right_edge_width, 0); + canvas->TileImageInt(top_edge, left_edge_width, 0, + view_->width() - left_edge_width - right_edge_width, + top_edge.height()); + + DCHECK_EQ(left_edge.height(), right_edge.height()); + int bottom = left_edge.height(); + int bottom_height = bottom_edge.height(); + canvas->TileImageInt(bottom_edge, left_edge_width, bottom - bottom_height, + view_->width() - left_edge_width - right_edge_width, + bottom_height); +} + +void DefaultHeaderPainter::PaintTitleBar(gfx::Canvas* canvas) { + // The window icon is painted by its own views::View. + gfx::Rect title_bounds = GetTitleBounds(); + title_bounds.set_x(view_->GetMirroredXForRect(title_bounds)); + canvas->DrawStringRectWithFlags( + frame_->widget_delegate()->GetWindowTitle(), GetTitleFontList(), + kTitleTextColor, title_bounds, gfx::Canvas::NO_SUBPIXEL_RENDERING); +} + +void DefaultHeaderPainter::PaintHeaderContentSeparator(gfx::Canvas* canvas) { + gfx::ScopedCanvas scoped_canvas(canvas); + const float scale = canvas->UndoDeviceScaleFactor(); + gfx::RectF rect(0, painted_height_ * scale - 1, view_->width() * scale, 1); + SkPaint paint; + paint.setColor((mode_ == MODE_ACTIVE) ? kHeaderContentSeparatorColor + : kHeaderContentSeparatorInactiveColor); + canvas->sk_canvas()->drawRect(gfx::RectFToSkRect(rect), paint); +} + +void DefaultHeaderPainter::LayoutLeftHeaderView() { + if (left_header_view_) { + // Vertically center the left header view with respect to the caption button + // container. + // Floor when computing the center of |caption_button_container_|. + gfx::Size size = left_header_view_->GetPreferredSize(); + int icon_offset_y = + caption_button_container_->height() / 2 - size.height() / 2; + left_header_view_->SetBounds(left_view_x_inset_, icon_offset_y, + size.width(), size.height()); + } +} + +bool DefaultHeaderPainter::ShouldUseLightImages() { + int luminance = color_utils::GetLuminanceForColor( + mode_ == MODE_INACTIVE ? inactive_frame_color_ : active_frame_color_); + return luminance < kMaxLuminanceForLightButtons; +} + +void DefaultHeaderPainter::UpdateAllButtonImages() { + bool use_light_images = ShouldUseLightImages(); + caption_button_container_->SetButtonImages( + CAPTION_BUTTON_ICON_MINIMIZE, + use_light_images ? IDR_MASH_WM_WINDOW_CONTROL_ICON_MINIMIZE_WHITE + : IDR_MASH_WM_WINDOW_CONTROL_ICON_MINIMIZE, + IDR_MASH_WM_WINDOW_CONTROL_BACKGROUND_H, + IDR_MASH_WM_WINDOW_CONTROL_BACKGROUND_P); + + UpdateSizeButtonImages(use_light_images); + + caption_button_container_->SetButtonImages( + CAPTION_BUTTON_ICON_CLOSE, + use_light_images ? IDR_MASH_WM_WINDOW_CONTROL_ICON_CLOSE_WHITE + : IDR_MASH_WM_WINDOW_CONTROL_ICON_CLOSE, + IDR_MASH_WM_WINDOW_CONTROL_BACKGROUND_H, + IDR_MASH_WM_WINDOW_CONTROL_BACKGROUND_P); + + caption_button_container_->SetButtonImages( + CAPTION_BUTTON_ICON_LEFT_SNAPPED, + use_light_images ? IDR_MASH_WM_WINDOW_CONTROL_ICON_LEFT_SNAPPED_WHITE + : IDR_MASH_WM_WINDOW_CONTROL_ICON_LEFT_SNAPPED, + IDR_MASH_WM_WINDOW_CONTROL_BACKGROUND_H, + IDR_MASH_WM_WINDOW_CONTROL_BACKGROUND_P); + + caption_button_container_->SetButtonImages( + CAPTION_BUTTON_ICON_RIGHT_SNAPPED, + use_light_images ? IDR_MASH_WM_WINDOW_CONTROL_ICON_RIGHT_SNAPPED_WHITE + : IDR_MASH_WM_WINDOW_CONTROL_ICON_RIGHT_SNAPPED, + IDR_MASH_WM_WINDOW_CONTROL_BACKGROUND_H, + IDR_MASH_WM_WINDOW_CONTROL_BACKGROUND_P); +} + +void DefaultHeaderPainter::UpdateSizeButtonImages(bool use_light_images) { + int icon_id = 0; + if (frame_->IsMaximized() || frame_->IsFullscreen()) { + icon_id = use_light_images ? IDR_MASH_WM_WINDOW_CONTROL_ICON_RESTORE_WHITE + : IDR_MASH_WM_WINDOW_CONTROL_ICON_RESTORE; + } else { + icon_id = use_light_images ? IDR_MASH_WM_WINDOW_CONTROL_ICON_MAXIMIZE_WHITE + : IDR_MASH_WM_WINDOW_CONTROL_ICON_MAXIMIZE; + } + caption_button_container_->SetButtonImages( + CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE, icon_id, + IDR_MASH_WM_WINDOW_CONTROL_BACKGROUND_H, + IDR_MASH_WM_WINDOW_CONTROL_BACKGROUND_P); +} + +gfx::Rect DefaultHeaderPainter::GetLocalBounds() const { + return gfx::Rect(view_->width(), painted_height_); +} + +gfx::Rect DefaultHeaderPainter::GetTitleBounds() const { + return HeaderPainterUtil::GetTitleBounds( + left_header_view_, caption_button_container_, GetTitleFontList()); +} + +bool DefaultHeaderPainter::UsesCustomFrameColors() const { + return active_frame_color_ != kDefaultFrameColor || + inactive_frame_color_ != kDefaultFrameColor; +} + +} // namespace wm +} // namespace mash diff --git a/mash/wm/frame/default_header_painter.h b/mash/wm/frame/default_header_painter.h new file mode 100644 index 0000000..5e59cb8 --- /dev/null +++ b/mash/wm/frame/default_header_painter.h @@ -0,0 +1,122 @@ +// Copyright 2015 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. + +#ifndef MASH_WM_FRAME_DEFAULT_HEADER_PAINTER_H_ +#define MASH_WM_FRAME_DEFAULT_HEADER_PAINTER_H_ + +#include "base/basictypes.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "mash/wm/frame/header_painter.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/animation/animation_delegate.h" + +namespace gfx { +class ImageSkia; +class Rect; +class SlideAnimation; +} +namespace views { +class View; +class Widget; +} + +namespace mash { +namespace wm { + +class FrameCaptionButtonContainerView; + +// Helper class for painting the default window header. +class DefaultHeaderPainter : public HeaderPainter, + public gfx::AnimationDelegate { + public: + DefaultHeaderPainter(); + ~DefaultHeaderPainter() override; + + // DefaultHeaderPainter does not take ownership of any of the parameters. + void Init(views::Widget* frame, + views::View* header_view, + FrameCaptionButtonContainerView* caption_button_container); + + // HeaderPainter overrides: + int GetMinimumHeaderWidth() const override; + void PaintHeader(gfx::Canvas* canvas, Mode mode) override; + void LayoutHeader() override; + int GetHeaderHeight() const override; + int GetHeaderHeightForPainting() const override; + void SetHeaderHeightForPainting(int height) override; + void SchedulePaintForTitle() override; + void UpdateLeftViewXInset(int left_view_x_inset) override; + + // Sets the left header view for the header. Passing NULL removes the view. + void UpdateLeftHeaderView(views::View* left_header_view); + + // Sets the active and inactive frame colors. Note the inactive frame color + // will have some transparency added when the frame is drawn. + void SetFrameColors(SkColor active_frame_color, SkColor inactive_frame_color); + + private: + // gfx::AnimationDelegate override: + void AnimationProgressed(const gfx::Animation* animation) override; + + // Paints highlight around the edge of the header for inactive restored + // windows. + void PaintHighlightForInactiveRestoredWindow(gfx::Canvas* canvas); + + // Paints the title bar, primarily the title string. + void PaintTitleBar(gfx::Canvas* canvas); + + // Paints the header/content separator. + void PaintHeaderContentSeparator(gfx::Canvas* canvas); + + // Layout the left header view. + void LayoutLeftHeaderView(); + + // Whether light caption images should be used. This is the case when the + // background of the frame is dark. + bool ShouldUseLightImages(); + + // Update all the images in the caption buttons. + void UpdateAllButtonImages(); + + // Updates the size button's images. + void UpdateSizeButtonImages(bool use_light_images); + + // Returns the header bounds in the coordinates of |view_|. The header is + // assumed to be positioned at the top left corner of |view_| and to have the + // same width as |view_|. + gfx::Rect GetLocalBounds() const; + + // Returns the bounds for the title. + gfx::Rect GetTitleBounds() const; + + // Returns whether the frame uses custom frame coloring. + bool UsesCustomFrameColors() const; + + views::Widget* frame_; + views::View* view_; + views::View* left_header_view_; // May be NULL. + int left_view_x_inset_; + SkColor active_frame_color_; + SkColor inactive_frame_color_; + FrameCaptionButtonContainerView* caption_button_container_; + + // The height of the header to paint. + int painted_height_; + + // Whether the header should be painted as active. + Mode mode_; + + // Whether the header is painted for the first time. + bool initial_paint_; + + scoped_ptr<gfx::SlideAnimation> activation_animation_; + + DISALLOW_COPY_AND_ASSIGN(DefaultHeaderPainter); +}; + +} // namespace wm +} // namespace mash + +#endif // MASH_WM_FRAME_DEFAULT_HEADER_PAINTER_H_ diff --git a/mash/wm/frame/frame_border_hit_test_controller.cc b/mash/wm/frame/frame_border_hit_test_controller.cc new file mode 100644 index 0000000..cd233f8 --- /dev/null +++ b/mash/wm/frame/frame_border_hit_test_controller.cc @@ -0,0 +1,82 @@ +// Copyright 2015 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 "mash/wm/frame/frame_border_hit_test_controller.h" + +#include "mash/wm/frame/caption_buttons/frame_caption_button_container_view.h" +#include "ui/aura/env.h" +#include "ui/aura/window.h" +#include "ui/base/hit_test.h" +#include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_delegate.h" +#include "ui/views/window/non_client_view.h" + +namespace mash { +namespace wm { + +// In the window corners, the resize areas don't actually expand bigger, but the +// 16 px at the end of each edge triggers diagonal resizing. +const int kResizeAreaCornerSize = 16; + +// Windows do not have a traditional visible window frame. Window content +// extends to the edge of the window. We consider a small region outside the +// window bounds and an even smaller region overlapping the window to be the +// "non-client" area and use it for resizing. +const int kResizeOutsideBoundsSize = 6; +const int kResizeOutsideBoundsScaleForTouch = 5; +const int kResizeInsideBoundsSize = 1; + +// static +int FrameBorderHitTestController::NonClientHitTest( + views::NonClientFrameView* view, + FrameCaptionButtonContainerView* caption_button_container, + const gfx::Point& point_in_widget) { + gfx::Rect expanded_bounds = view->bounds(); + int outside_bounds = kResizeOutsideBoundsSize; + + if (aura::Env::GetInstance()->is_touch_down()) + outside_bounds *= kResizeOutsideBoundsScaleForTouch; + expanded_bounds.Inset(-outside_bounds, -outside_bounds); + + if (!expanded_bounds.Contains(point_in_widget)) + return HTNOWHERE; + + // Check the frame first, as we allow a small area overlapping the contents + // to be used for resize handles. + views::Widget* frame = view->GetWidget(); + bool can_ever_resize = frame->widget_delegate()->CanResize(); + // Don't allow overlapping resize handles when the window is maximized or + // fullscreen, as it can't be resized in those states. + int resize_border = kResizeInsideBoundsSize; + if (frame->IsMaximized() || frame->IsFullscreen()) { + resize_border = 0; + can_ever_resize = false; + } + int frame_component = view->GetHTComponentForFrame( + point_in_widget, resize_border, resize_border, kResizeAreaCornerSize, + kResizeAreaCornerSize, can_ever_resize); + if (frame_component != HTNOWHERE) + return frame_component; + + int client_component = + frame->client_view()->NonClientHitTest(point_in_widget); + if (client_component != HTNOWHERE) + return client_component; + + if (caption_button_container->visible()) { + gfx::Point point_in_caption_button_container(point_in_widget); + views::View::ConvertPointFromWidget(caption_button_container, + &point_in_caption_button_container); + int caption_button_component = caption_button_container->NonClientHitTest( + point_in_caption_button_container); + if (caption_button_component != HTNOWHERE) + return caption_button_component; + } + + // Caption is a safe default. + return HTCAPTION; +} + +} // namespace wm +} // namespace mash diff --git a/mash/wm/frame/frame_border_hit_test_controller.h b/mash/wm/frame/frame_border_hit_test_controller.h new file mode 100644 index 0000000..bcfbb77 --- /dev/null +++ b/mash/wm/frame/frame_border_hit_test_controller.h @@ -0,0 +1,40 @@ +// Copyright 2015 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. + +#ifndef MASH_WM_FRAME_FRAME_BORDER_HITTEST_CONTROLLER_H_ +#define MASH_WM_FRAME_FRAME_BORDER_HITTEST_CONTROLLER_H_ + +#include "base/macros.h" + +namespace gfx { +class Point; +} + +namespace views { +class NonClientFrameView; +class Widget; +} + +namespace mash { +namespace wm { +class FrameCaptionButtonContainerView; + +// Class which manages the hittest override bounds for |frame|. +class FrameBorderHitTestController { + public: + // Does the non client hit test on behalf of |view|. |point_in_widget| must be + // in the coordinates of |view|'s widget. + static int NonClientHitTest( + views::NonClientFrameView* view, + FrameCaptionButtonContainerView* caption_button_container, + const gfx::Point& point_in_widget); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(FrameBorderHitTestController); +}; + +} // namespace wm +} // namespace mash + +#endif // MASH_WM_FRAME_FRAME_BORDER_HITTEST_CONTROLLER_H_ diff --git a/mash/wm/frame/header_painter.h b/mash/wm/frame/header_painter.h new file mode 100644 index 0000000..84eef20 --- /dev/null +++ b/mash/wm/frame/header_painter.h @@ -0,0 +1,52 @@ +// Copyright 2015 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. + +#ifndef MASH_WM_FRAME_HEADER_PAINTER_H_ +#define MASH_WM_FRAME_HEADER_PAINTER_H_ + +namespace gfx { +class Canvas; +} + +namespace mash { +namespace wm { + +// Helper class for painting the window header. +// TODO(sky): keep this only if we're going to actually need different +// subclasses. +class HeaderPainter { + public: + enum Mode { MODE_ACTIVE, MODE_INACTIVE }; + + virtual ~HeaderPainter() {} + + // Returns the header's minimum width. + virtual int GetMinimumHeaderWidth() const = 0; + + // Paints the header. + virtual void PaintHeader(gfx::Canvas* canvas, Mode mode) = 0; + + // Performs layout for the header. + virtual void LayoutHeader() = 0; + + // Get the height of the header. + virtual int GetHeaderHeight() const = 0; + + // Gets / sets how much of the header is painted. This allows the header to + // paint under things (like the tabstrip) which have transparent / + // non-painting sections. This height does not affect LayoutHeader(). + virtual int GetHeaderHeightForPainting() const = 0; + virtual void SetHeaderHeightForPainting(int height_for_painting) = 0; + + // Schedule a re-paint of the entire title. + virtual void SchedulePaintForTitle() = 0; + + // Updates the x inset of the leftmost view in the header. + virtual void UpdateLeftViewXInset(int left_view_x_inset) = 0; +}; + +} // namespace wm +} // namespace mash + +#endif // MASH_WM_FRAME_HEADER_PAINTER_H_ diff --git a/mash/wm/frame/header_painter_util.cc b/mash/wm/frame/header_painter_util.cc new file mode 100644 index 0000000..a658d16 --- /dev/null +++ b/mash/wm/frame/header_painter_util.cc @@ -0,0 +1,80 @@ +// Copyright 2015 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 "mash/wm/frame/header_painter_util.h" + +#include <algorithm> + +#include "ui/gfx/font_list.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" + +namespace { + +// Radius of the header's top corners when the window is restored. +const int kTopCornerRadiusWhenRestored = 2; + +// Distance between left edge of the window and the leftmost view. +const int kDefaultLeftViewXInset = 9; + +// Space between the title text and the caption buttons. +const int kTitleCaptionButtonSpacing = 5; + +// Space between window icon and title text. +const int kTitleIconOffsetX = 5; + +// Space between window edge and title text, when there is no icon. +const int kTitleNoIconOffsetX = 8; + +// In the pre-Ash era the web content area had a frame along the left edge, so +// user-generated theme images for the new tab page assume they are shifted +// right relative to the header. Now that we have removed the left edge frame +// we need to copy the theme image for the window header from a few pixels +// inset to preserve alignment with the NTP image, or else we'll break a bunch +// of existing themes. We do something similar on OS X for the same reason. +const int kThemeFrameImageInsetX = 5; + +} // namespace + +namespace mash { +namespace wm { + +// static +int HeaderPainterUtil::GetTopCornerRadiusWhenRestored() { + return kTopCornerRadiusWhenRestored; +} + +// static +int HeaderPainterUtil::GetDefaultLeftViewXInset() { + return kDefaultLeftViewXInset; +} + +// static +int HeaderPainterUtil::GetThemeBackgroundXInset() { + return kThemeFrameImageInsetX; +} + +// static +gfx::Rect HeaderPainterUtil::GetTitleBounds( + const views::View* left_view, + const views::View* right_view, + const gfx::FontList& title_font_list) { + int x = left_view ? left_view->bounds().right() + kTitleIconOffsetX + : kTitleNoIconOffsetX; + int height = title_font_list.GetHeight(); + // Floor when computing the center of |caption_button_container| and when + // computing the center of the text. + int y = std::max(0, (right_view->height() / 2) - (height / 2)); + int width = std::max(0, right_view->x() - kTitleCaptionButtonSpacing - x); + return gfx::Rect(x, y, width, height); +} + +// static +bool HeaderPainterUtil::CanAnimateActivation(views::Widget* widget) { + return true; +} + +} // namespace wm +} // namespace mash diff --git a/mash/wm/frame/header_painter_util.h b/mash/wm/frame/header_painter_util.h new file mode 100644 index 0000000..230c832 --- /dev/null +++ b/mash/wm/frame/header_painter_util.h @@ -0,0 +1,56 @@ +// Copyright 2015 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. + +#ifndef MASH_WM_FRAME_HEADER_PAINTER_UTIL_H_ +#define MASH_WM_FRAME_HEADER_PAINTER_UTIL_H_ + +#include "base/macros.h" + +namespace gfx { +class FontList; +class Rect; +} +namespace views { +class View; +class Widget; +} + +namespace mash { +namespace wm { + +// Static-only helper class for functionality used accross multiple +// implementations of HeaderPainter. +class HeaderPainterUtil { + public: + // Returns the radius of the header's corners when the window is restored. + static int GetTopCornerRadiusWhenRestored(); + + // Returns the default distance between the left edge of the window and the + // leftmost view in the header. + static int GetDefaultLeftViewXInset(); + + // Returns the amount that the frame background is inset from the left edge of + // the window. + static int GetThemeBackgroundXInset(); + + // Returns the 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. + static gfx::Rect GetTitleBounds(const views::View* left_view, + const views::View* right_view, + const gfx::FontList& title_font_list); + + // 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. + static bool CanAnimateActivation(views::Widget* widget); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(HeaderPainterUtil); +}; + +} // namespace wm +} // namespace mash + +#endif // MASH_WM_FRAME_HEADER_PAINTER_UTIL_H_ diff --git a/mash/wm/move_loop.cc b/mash/wm/frame/move_loop.cc index 6c3ee7d..786ff76 100644 --- a/mash/wm/move_loop.cc +++ b/mash/wm/frame/move_loop.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "mash/wm/move_loop.h" +#include "mash/wm/frame/move_loop.h" #include "base/auto_reset.h" #include "components/mus/public/cpp/window.h" @@ -11,6 +11,9 @@ #include "ui/gfx/geometry/point_conversions.h" #include "ui/gfx/geometry/rect.h" +namespace mash { +namespace wm { + namespace { gfx::Point EventLocationToPoint(const mus::mojom::Event& event) { @@ -221,3 +224,6 @@ void MoveLoop::OnWindowVisibilityChanged(mus::Window* window) { DCHECK_EQ(window, target_); Cancel(); } + +} // namespace wm +} // namespace mash diff --git a/mash/wm/move_loop.h b/mash/wm/frame/move_loop.h index fd1a3e7..12ca46e 100644 --- a/mash/wm/move_loop.h +++ b/mash/wm/frame/move_loop.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef MASH_WM_MOVE_LOOP_H_ -#define MASH_WM_MOVE_LOOP_H_ +#ifndef MASH_WM_FRAME_MOVE_LOOP_H_ +#define MASH_WM_FRAME_MOVE_LOOP_H_ #include "base/macros.h" #include "base/memory/scoped_ptr.h" @@ -12,6 +12,9 @@ #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" +namespace mash { +namespace wm { + // MoveLoop is responsible for moving/resizing windows. class MoveLoop : public mus::WindowObserver { public: @@ -112,4 +115,7 @@ class MoveLoop : public mus::WindowObserver { DISALLOW_COPY_AND_ASSIGN(MoveLoop); }; -#endif // MASH_WM_MOVE_LOOP_H_ +} // namespace wm +} // namespace mash + +#endif // MASH_WM_FRAME_MOVE_LOOP_H_ diff --git a/mash/wm/move_loop_unittest.cc b/mash/wm/frame/move_loop_unittest.cc index 462c7df7..86ca32c 100644 --- a/mash/wm/move_loop_unittest.cc +++ b/mash/wm/frame/move_loop_unittest.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "mash/wm/move_loop.h" +#include "mash/wm/frame/move_loop.h" #include "components/mus/public/cpp/tests/test_window.h" #include "mojo/converters/input_events/input_events_type_converters.h" @@ -13,6 +13,9 @@ using MoveLoopTest = testing::Test; +namespace mash { +namespace wm { + namespace { // Sets the client area for |window|. Padding is provided on the top so that @@ -177,3 +180,6 @@ TEST_F(MoveLoopTest, Move) { ASSERT_EQ(data[i].expected_bounds, window.bounds()); } } + +} // namespace wm +} // namespace mash diff --git a/mash/wm/frame/non_client_frame_view_mash.cc b/mash/wm/frame/non_client_frame_view_mash.cc new file mode 100644 index 0000000..f3bee2d --- /dev/null +++ b/mash/wm/frame/non_client_frame_view_mash.cc @@ -0,0 +1,351 @@ +// Copyright 2015 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 "mash/wm/frame/non_client_frame_view_mash.h" + +#include <algorithm> +#include <vector> + +#include "components/mus/public/cpp/window.h" +#include "grit/mash_wm_resources.h" +#include "mash/wm/frame/caption_buttons/frame_caption_button_container_view.h" +#include "mash/wm/frame/default_header_painter.h" +#include "mash/wm/frame/frame_border_hit_test_controller.h" +#include "mash/wm/frame/header_painter.h" +#include "mash/wm/frame/move_loop.h" +#include "mojo/converters/input_events/input_events_type_converters.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/compositor/paint_recorder.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_conversions.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/image/image.h" +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_delegate.h" + +namespace mash { +namespace wm { + +/////////////////////////////////////////////////////////////////////////////// +// NonClientFrameViewMash::HeaderView + +// View which paints the header. +class NonClientFrameViewMash::HeaderView : public views::View { + public: + // |frame| is the widget that the caption buttons act on. + explicit HeaderView(views::Widget* frame); + ~HeaderView() override; + + // Schedules a repaint for the entire title. + void SchedulePaintForTitle(); + + // Tells the window controls to reset themselves to the normal state. + void ResetWindowControls(); + + // Returns the view's preferred height. + int GetPreferredHeight() const; + + // Returns the view's minimum width. + int GetMinimumWidth() const; + + void SizeConstraintsChanged(); + + void SetFrameColors(SkColor active_frame_color, SkColor inactive_frame_color); + + // views::View: + void Layout() override; + void OnPaint(gfx::Canvas* canvas) override; + void ChildPreferredSizeChanged(views::View* child) override; + + FrameCaptionButtonContainerView* caption_button_container() { + return caption_button_container_; + } + + private: + // The widget that the caption buttons act on. + views::Widget* frame_; + + // Helper for painting the header. + scoped_ptr<DefaultHeaderPainter> header_painter_; + + // View which contains the window caption buttons. + FrameCaptionButtonContainerView* caption_button_container_; + + DISALLOW_COPY_AND_ASSIGN(HeaderView); +}; + +NonClientFrameViewMash::HeaderView::HeaderView(views::Widget* frame) + : frame_(frame), + header_painter_(new DefaultHeaderPainter), + caption_button_container_(nullptr) { + caption_button_container_ = new FrameCaptionButtonContainerView(frame_); + caption_button_container_->UpdateSizeButtonVisibility(); + AddChildView(caption_button_container_); + + header_painter_->Init(frame_, this, caption_button_container_); +} + +NonClientFrameViewMash::HeaderView::~HeaderView() {} + +void NonClientFrameViewMash::HeaderView::SchedulePaintForTitle() { + header_painter_->SchedulePaintForTitle(); +} + +void NonClientFrameViewMash::HeaderView::ResetWindowControls() { + caption_button_container_->ResetWindowControls(); +} + +int NonClientFrameViewMash::HeaderView::GetPreferredHeight() const { + return header_painter_->GetHeaderHeightForPainting(); +} + +int NonClientFrameViewMash::HeaderView::GetMinimumWidth() const { + return header_painter_->GetMinimumHeaderWidth(); +} + +void NonClientFrameViewMash::HeaderView::SizeConstraintsChanged() { + caption_button_container_->ResetWindowControls(); + caption_button_container_->UpdateSizeButtonVisibility(); + Layout(); +} + +void NonClientFrameViewMash::HeaderView::SetFrameColors( + SkColor active_frame_color, + SkColor inactive_frame_color) { + header_painter_->SetFrameColors(active_frame_color, inactive_frame_color); +} + +/////////////////////////////////////////////////////////////////////////////// +// NonClientFrameViewMash::HeaderView, views::View overrides: + +void NonClientFrameViewMash::HeaderView::Layout() { + header_painter_->LayoutHeader(); +} + +void NonClientFrameViewMash::HeaderView::OnPaint(gfx::Canvas* canvas) { + bool paint_as_active = + frame_->non_client_view()->frame_view()->ShouldPaintAsActive(); + caption_button_container_->SetPaintAsActive(paint_as_active); + + HeaderPainter::Mode header_mode = paint_as_active + ? HeaderPainter::MODE_ACTIVE + : HeaderPainter::MODE_INACTIVE; + header_painter_->PaintHeader(canvas, header_mode); +} + +void NonClientFrameViewMash::HeaderView::ChildPreferredSizeChanged( + views::View* child) { + // FrameCaptionButtonContainerView animates the visibility changes in + // UpdateSizeButtonVisibility(false). Due to this a new size is not available + // until the completion of the animation. Layout in response to the preferred + // size changes. + if (child != caption_button_container_) + return; + parent()->Layout(); +} + +//////////////////////////////////////////////////////////////////////////////// +// NonClientFrameViewMash, public: + +// static +const char NonClientFrameViewMash::kViewClassName[] = "NonClientFrameViewMash"; + +NonClientFrameViewMash::NonClientFrameViewMash(views::Widget* frame, + mus::Window* window) + : frame_(frame), window_(window), header_view_(new HeaderView(frame)) { + // |header_view_| is set as the non client view's overlay view so that it can + // overlay the web contents in immersive fullscreen. + AddChildView(header_view_); + window_->AddObserver(this); +} + +NonClientFrameViewMash::~NonClientFrameViewMash() { + if (window_) + window_->RemoveObserver(this); +} + +// static +gfx::Insets NonClientFrameViewMash::GetPreferredClientAreaInsets() { + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + const int header_height = + rb.GetImageSkiaNamed(IDR_MASH_WM_WINDOW_CONTROL_BACKGROUND_P) + ->size() + .height(); + return gfx::Insets(header_height, 0, 0, 0); +} + +void NonClientFrameViewMash::SetFrameColors(SkColor active_frame_color, + SkColor inactive_frame_color) { + header_view_->SetFrameColors(active_frame_color, inactive_frame_color); +} + +//////////////////////////////////////////////////////////////////////////////// +// NonClientFrameViewMash, views::NonClientFrameView overrides: + +gfx::Rect NonClientFrameViewMash::GetBoundsForClientView() const { + gfx::Rect result(GetLocalBounds()); + result.Inset(window_->client_area()); + return result; +} + +gfx::Rect NonClientFrameViewMash::GetWindowBoundsForClientBounds( + const gfx::Rect& client_bounds) const { + gfx::Rect window_bounds = client_bounds; + window_bounds.Inset( + window_->client_area().left(), window_->client_area().top(), + window_->client_area().right(), window_->client_area().bottom()); + return window_bounds; +} + +int NonClientFrameViewMash::NonClientHitTest(const gfx::Point& point) { + return FrameBorderHitTestController::NonClientHitTest( + this, header_view_->caption_button_container(), point); +} + +void NonClientFrameViewMash::GetWindowMask(const gfx::Size& size, + gfx::Path* window_mask) {} + +void NonClientFrameViewMash::ResetWindowControls() { + header_view_->ResetWindowControls(); +} + +void NonClientFrameViewMash::UpdateWindowIcon() {} + +void NonClientFrameViewMash::UpdateWindowTitle() { + header_view_->SchedulePaintForTitle(); +} + +void NonClientFrameViewMash::SizeConstraintsChanged() { + header_view_->SizeConstraintsChanged(); +} + +//////////////////////////////////////////////////////////////////////////////// +// NonClientFrameViewMash, views::View overrides: + +void NonClientFrameViewMash::Layout() { + header_view_->SetBounds(0, 0, width(), header_view_->GetPreferredHeight()); + header_view_->Layout(); +} + +gfx::Size NonClientFrameViewMash::GetPreferredSize() const { + gfx::Size pref = frame_->client_view()->GetPreferredSize(); + return frame_->non_client_view() + ->GetWindowBoundsForClientBounds(gfx::Rect(pref)) + .size(); +} + +const char* NonClientFrameViewMash::GetClassName() const { + return kViewClassName; +} + +gfx::Size NonClientFrameViewMash::GetMinimumSize() const { + gfx::Size min_client_view_size(frame_->client_view()->GetMinimumSize()); + return gfx::Size( + std::max(header_view_->GetMinimumWidth(), min_client_view_size.width()), + NonClientTopBorderHeight() + min_client_view_size.height()); +} + +gfx::Size NonClientFrameViewMash::GetMaximumSize() const { + gfx::Size max_client_size(frame_->client_view()->GetMaximumSize()); + int width = 0; + int height = 0; + + if (max_client_size.width() > 0) + width = std::max(header_view_->GetMinimumWidth(), max_client_size.width()); + if (max_client_size.height() > 0) + height = NonClientTopBorderHeight() + max_client_size.height(); + + return gfx::Size(width, height); +} + +void NonClientFrameViewMash::OnPaint(gfx::Canvas* canvas) { + canvas->Save(); + NonClientFrameView::OnPaint(canvas); + canvas->Restore(); + + // The client app draws the client area. Make ours totally transparent so + // we only see the client apps client area. + canvas->FillRect(GetBoundsForClientView(), SK_ColorBLACK, + SkXfermode::kSrc_Mode); +} + +void NonClientFrameViewMash::PaintChildren(const ui::PaintContext& context) { + NonClientFrameView::PaintChildren(context); + + // The client app draws the client area. Make ours totally transparent so + // we only see the client apps client area. + ui::PaintRecorder recorder(context, size(), &paint_cache_); + recorder.canvas()->FillRect(GetBoundsForClientView(), SK_ColorBLACK, + SkXfermode::kSrc_Mode); +} + +bool NonClientFrameViewMash::OnMousePressed(const ui::MouseEvent& event) { + return StartMoveLoopIfNecessary(event); +} + +bool NonClientFrameViewMash::OnMouseDragged(const ui::MouseEvent& event) { + ContinueMove(event); + return move_loop_.get() != nullptr; +} + +void NonClientFrameViewMash::OnMouseReleased(const ui::MouseEvent& event) { + ContinueMove(event); +} + +void NonClientFrameViewMash::OnMouseCaptureLost() { + StopMove(); +} + +void NonClientFrameViewMash::OnWindowClientAreaChanged( + mus::Window* window, + const gfx::Insets& old_client_area) { + Layout(); + // NonClientView (our parent) positions the client view based on bounds from + // us. We need to layout from parent to trigger a layout of the client view. + if (parent()) + parent()->Layout(); + SchedulePaint(); +} + +void NonClientFrameViewMash::OnWindowDestroyed(mus::Window* window) { + window_->RemoveObserver(this); + window_ = nullptr; +} + +bool NonClientFrameViewMash::StartMoveLoopIfNecessary(const ui::Event& event) { + if (move_loop_) + return false; + // TODO(sky): convert MoveLoop to take ui::Event. + // TODO(sky): pass in hit test result. + move_loop_ = MoveLoop::Create(window_, *mus::mojom::Event::From(event)); + return true; +} + +void NonClientFrameViewMash::ContinueMove(const ui::Event& event) { + // TODO(sky): convert MoveLoop to take ui::Event. + if (move_loop_ && + move_loop_->Move(*mus::mojom::Event::From(event)) == MoveLoop::DONE) { + move_loop_.reset(); + } +} + +void NonClientFrameViewMash::StopMove() { + move_loop_.reset(); +} + +views::View* NonClientFrameViewMash::GetHeaderView() { + return header_view_; +} + +//////////////////////////////////////////////////////////////////////////////// +// NonClientFrameViewMash, private: + +int NonClientFrameViewMash::NonClientTopBorderHeight() const { + return header_view_->GetPreferredHeight(); +} + +} // namespace wm +} // namespace mash diff --git a/mash/wm/frame/non_client_frame_view_mash.h b/mash/wm/frame/non_client_frame_view_mash.h new file mode 100644 index 0000000..31b6880 --- /dev/null +++ b/mash/wm/frame/non_client_frame_view_mash.h @@ -0,0 +1,107 @@ +// Copyright 2015 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. + +#ifndef MASH_WM_FRAME_NON_CLIENT_FRAME_VIEW_MASH_H_ +#define MASH_WM_FRAME_NON_CLIENT_FRAME_VIEW_MASH_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "components/mus/public/cpp/window_observer.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/compositor/paint_cache.h" +#include "ui/views/window/non_client_view.h" + +namespace gfx { +class Insets; +} + +namespace mus { +class Window; +} + +namespace views { +class Widget; +} + +namespace mash { +namespace wm { + +class FrameCaptionButtonContainerView; +class MoveLoop; + +class NonClientFrameViewMash : public views::NonClientFrameView, + public mus::WindowObserver { + public: + // Internal class name. + static const char kViewClassName[]; + + NonClientFrameViewMash(views::Widget* frame, mus::Window* window); + ~NonClientFrameViewMash() override; + + static gfx::Insets GetPreferredClientAreaInsets(); + + // Sets the active and inactive frame colors. Note the inactive frame color + // will have some transparency added when the frame is drawn. + void SetFrameColors(SkColor active_frame_color, SkColor inactive_frame_color); + + // views::NonClientFrameView: + gfx::Rect GetBoundsForClientView() const override; + gfx::Rect GetWindowBoundsForClientBounds( + const gfx::Rect& client_bounds) const override; + int NonClientHitTest(const gfx::Point& point) override; + void GetWindowMask(const gfx::Size& size, gfx::Path* window_mask) override; + void ResetWindowControls() override; + void UpdateWindowIcon() override; + void UpdateWindowTitle() override; + void SizeConstraintsChanged() override; + + // views::View: + void Layout() override; + gfx::Size GetPreferredSize() const override; + const char* GetClassName() const override; + gfx::Size GetMinimumSize() const override; + gfx::Size GetMaximumSize() const override; + void OnPaint(gfx::Canvas* canvas) override; + void PaintChildren(const ui::PaintContext& context) override; + bool OnMousePressed(const ui::MouseEvent& event) override; + bool OnMouseDragged(const ui::MouseEvent& event) override; + void OnMouseReleased(const ui::MouseEvent& event) override; + void OnMouseCaptureLost() override; + + // mus::WindowObserver: + void OnWindowClientAreaChanged(mus::Window* window, + const gfx::Insets& old_client_area) override; + void OnWindowDestroyed(mus::Window* window) override; + + // Get the view of the header. + views::View* GetHeaderView(); + + private: + class OverlayView; + + // Height from top of window to top of client area. + int NonClientTopBorderHeight() const; + + bool StartMoveLoopIfNecessary(const ui::Event& event); + void ContinueMove(const ui::Event& event); + void StopMove(); + + // Not owned. + views::Widget* frame_; + + mus::Window* window_; + ui::PaintCache paint_cache_; + scoped_ptr<MoveLoop> move_loop_; + + // View which contains the title and window controls. + class HeaderView; + HeaderView* header_view_; + + DISALLOW_COPY_AND_ASSIGN(NonClientFrameViewMash); +}; + +} // namespace wm +} // namespace mash + +#endif // MASH_WM_FRAME_NON_CLIENT_FRAME_VIEW_MASH_H_ diff --git a/mash/wm/non_client_frame_controller.cc b/mash/wm/non_client_frame_controller.cc index b9a95ed..b0eef4e 100644 --- a/mash/wm/non_client_frame_controller.cc +++ b/mash/wm/non_client_frame_controller.cc @@ -5,7 +5,7 @@ #include "mash/wm/non_client_frame_controller.h" #include "components/mus/public/cpp/window.h" -#include "mash/wm/non_client_frame_view_impl.h" +#include "mash/wm/frame/non_client_frame_view_mash.h" #include "mash/wm/property_util.h" #include "ui/views/mus/native_widget_mus.h" #include "ui/views/widget/widget.h" @@ -25,9 +25,10 @@ class WmNativeWidgetMus : public views::NativeWidgetMus { // NativeWidgetMus: views::NonClientFrameView* CreateNonClientFrameView() override { - NonClientFrameViewImpl* frame_view = new NonClientFrameViewImpl(window()); - frame_view->Init( - static_cast<views::internal::NativeWidgetPrivate*>(this)->GetWidget()); + views::Widget* widget = + static_cast<views::internal::NativeWidgetPrivate*>(this)->GetWidget(); + mash::wm::NonClientFrameViewMash* frame_view = + new mash::wm::NonClientFrameViewMash(widget, window()); return frame_view; } void CenterWindow(const gfx::Size& size) override { @@ -56,6 +57,11 @@ NonClientFrameController::NonClientFrameController(mojo::Shell* shell, widget_->Show(); } +// static +gfx::Insets NonClientFrameController::GetPreferredClientAreaInsets() { + return mash::wm::NonClientFrameViewMash::GetPreferredClientAreaInsets(); +} + NonClientFrameController::~NonClientFrameController() { if (window_) window_->RemoveObserver(this); diff --git a/mash/wm/non_client_frame_controller.h b/mash/wm/non_client_frame_controller.h index dfedd07..5e5918c 100644 --- a/mash/wm/non_client_frame_controller.h +++ b/mash/wm/non_client_frame_controller.h @@ -9,6 +9,10 @@ #include "components/mus/public/cpp/window_observer.h" #include "ui/views/widget/widget_delegate.h" +namespace gfx { +class Insets; +} + namespace mojo { class Shell; } @@ -24,6 +28,9 @@ class NonClientFrameController : public views::WidgetDelegateView, // NonClientFrameController deletes itself when |window| is destroyed. NonClientFrameController(mojo::Shell* shell, mus::Window* window); + // Returns the preferred client area insets. + static gfx::Insets GetPreferredClientAreaInsets(); + private: ~NonClientFrameController() override; diff --git a/mash/wm/non_client_frame_view_impl.cc b/mash/wm/non_client_frame_view_impl.cc deleted file mode 100644 index 976143b..0000000 --- a/mash/wm/non_client_frame_view_impl.cc +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2015 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 "mash/wm/non_client_frame_view_impl.h" - -#include "components/mus/public/cpp/window.h" -#include "mash/wm/move_loop.h" -#include "mojo/converters/input_events/input_events_type_converters.h" -#include "ui/compositor/paint_recorder.h" -#include "ui/gfx/canvas.h" - -NonClientFrameViewImpl::NonClientFrameViewImpl(mus::Window* window) - : window_(window) { - window_->AddObserver(this); -} - -NonClientFrameViewImpl::~NonClientFrameViewImpl() { - if (window_) - window_->RemoveObserver(this); -} - -gfx::Rect NonClientFrameViewImpl::GetBoundsForClientView() const { - gfx::Rect result(GetLocalBounds()); - result.Inset(window_->client_area()); - return result; -} - -void NonClientFrameViewImpl::OnPaint(gfx::Canvas* canvas) { - canvas->Save(); - CustomFrameView::OnPaint(canvas); - canvas->Restore(); - - // The client app draws the client area. Make ours totally transparent so - // we only see the client apps client area. - canvas->FillRect(GetBoundsForClientView(), SK_ColorBLACK, - SkXfermode::kSrc_Mode); -} - -void NonClientFrameViewImpl::PaintChildren(const ui::PaintContext& context) { - CustomFrameView::PaintChildren(context); - - // The client app draws the client area. Make ours totally transparent so - // we only see the client apps client area. - ui::PaintRecorder recorder(context, size(), &paint_cache_); - recorder.canvas()->FillRect(GetBoundsForClientView(), SK_ColorBLACK, - SkXfermode::kSrc_Mode); -} - -bool NonClientFrameViewImpl::OnMousePressed(const ui::MouseEvent& event) { - return StartMoveLoopIfNecessary(event); -} - -bool NonClientFrameViewImpl::OnMouseDragged(const ui::MouseEvent& event) { - ContinueMove(event); - return move_loop_.get() != nullptr; -} - -void NonClientFrameViewImpl::OnMouseReleased(const ui::MouseEvent& event) { - ContinueMove(event); -} - -void NonClientFrameViewImpl::OnMouseCaptureLost() { - StopMove(); -} - -void NonClientFrameViewImpl::OnWindowClientAreaChanged( - mus::Window* window, - const gfx::Insets& old_client_area) { - Layout(); - // NonClientView (our parent) positions the client view based on bounds from - // us. We need to layout from parent to trigger a layout of the client view. - if (parent()) - parent()->Layout(); - SchedulePaint(); -} - -void NonClientFrameViewImpl::OnWindowDestroyed(mus::Window* window) { - window_->RemoveObserver(this); - window_ = nullptr; -} - -bool NonClientFrameViewImpl::StartMoveLoopIfNecessary(const ui::Event& event) { - if (move_loop_) - return false; - // TODO(sky): convert MoveLoop to take ui::Event. - move_loop_ = MoveLoop::Create(window_, *mus::mojom::Event::From(event)); - return true; -} - -void NonClientFrameViewImpl::ContinueMove(const ui::Event& event) { - // TODO(sky): convert MoveLoop to take ui::Event. - if (move_loop_ && - move_loop_->Move(*mus::mojom::Event::From(event)) == MoveLoop::DONE) { - move_loop_.reset(); - } -} - -void NonClientFrameViewImpl::StopMove() { - move_loop_.reset(); -} diff --git a/mash/wm/non_client_frame_view_impl.h b/mash/wm/non_client_frame_view_impl.h deleted file mode 100644 index e7e3eef..0000000 --- a/mash/wm/non_client_frame_view_impl.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2015 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. - -#ifndef MASH_WM_NON_CLIENT_FRAME_VIEW_IMPL_H_ -#define MASH_WM_NON_CLIENT_FRAME_VIEW_IMPL_H_ - -#include "base/macros.h" -#include "base/memory/scoped_ptr.h" -#include "components/mus/public/cpp/window_observer.h" -#include "ui/compositor/paint_cache.h" -#include "ui/views/window/custom_frame_view.h" - -class MoveLoop; - -class NonClientFrameViewImpl : public views::CustomFrameView, - public mus::WindowObserver { - public: - explicit NonClientFrameViewImpl(mus::Window* window); - ~NonClientFrameViewImpl() override; - - private: - // CustomFrameView: - gfx::Rect GetBoundsForClientView() const override; - void OnPaint(gfx::Canvas* canvas) override; - void PaintChildren(const ui::PaintContext& context) override; - bool OnMousePressed(const ui::MouseEvent& event) override; - bool OnMouseDragged(const ui::MouseEvent& event) override; - void OnMouseReleased(const ui::MouseEvent& event) override; - void OnMouseCaptureLost() override; - - // mus::WindowObserver: - void OnWindowClientAreaChanged(mus::Window* window, - const gfx::Insets& old_client_area) override; - void OnWindowDestroyed(mus::Window* window) override; - - private: - bool StartMoveLoopIfNecessary(const ui::Event& event); - void ContinueMove(const ui::Event& event); - void StopMove(); - - mus::Window* window_; - ui::PaintCache paint_cache_; - scoped_ptr<MoveLoop> move_loop_; - - DISALLOW_COPY_AND_ASSIGN(NonClientFrameViewImpl); -}; - -#endif // MASH_WM_NON_CLIENT_FRAME_VIEW_IMPL_H_ diff --git a/mash/wm/window_manager_application.cc b/mash/wm/window_manager_application.cc index 65ec47c..bf58d79 100644 --- a/mash/wm/window_manager_application.cc +++ b/mash/wm/window_manager_application.cc @@ -63,7 +63,7 @@ void WindowManagerApplication::OnEmbed(mus::Window* root) { window_manager_.reset(new WindowManagerImpl(this)); ui_init_.reset(new ui::mojo::UIInit(views::GetDisplaysFromWindow(root))); - aura_init_.reset(new views::AuraInit(app_, "views_mus_resources.pak")); + aura_init_.reset(new views::AuraInit(app_, "mash_wm_resources.pak")); for (auto request : requests_) window_manager_binding_.AddBinding(window_manager_.get(), request->Pass()); diff --git a/mash/wm/window_manager_impl.cc b/mash/wm/window_manager_impl.cc index b5c2b09..46919aa 100644 --- a/mash/wm/window_manager_impl.cc +++ b/mash/wm/window_manager_impl.cc @@ -10,7 +10,6 @@ #include "components/mus/public/cpp/window_property.h" #include "components/mus/public/cpp/window_tree_connection.h" #include "components/mus/public/interfaces/input_events.mojom.h" -#include "mash/wm/move_loop.h" #include "mash/wm/non_client_frame_controller.h" #include "mash/wm/property_util.h" #include "mash/wm/public/interfaces/container.mojom.h" @@ -102,17 +101,11 @@ void WindowManagerImpl::GetConfig(const GetConfigCallback& callback) { // The insets are roughly what is needed by CustomFrameView. The expectation // is at some point we'll write our own NonClientFrameView and get the insets // from it. - config->normal_client_area_insets = mojo::Insets::New(); - config->normal_client_area_insets->top = 23; - config->normal_client_area_insets->left = 5; - config->normal_client_area_insets->right = 5; - config->normal_client_area_insets->bottom = 5; - - config->maximized_client_area_insets = mojo::Insets::New(); - config->maximized_client_area_insets->top = 21; - config->maximized_client_area_insets->left = 0; - config->maximized_client_area_insets->right = 0; - config->maximized_client_area_insets->bottom = 0; + const gfx::Insets client_area_insets = + NonClientFrameController::GetPreferredClientAreaInsets(); + config->normal_client_area_insets = mojo::Insets::From(client_area_insets); + + config->maximized_client_area_insets = mojo::Insets::From(client_area_insets); callback.Run(config.Pass()); } |