summaryrefslogtreecommitdiffstats
path: root/mash/wm/frame
diff options
context:
space:
mode:
Diffstat (limited to 'mash/wm/frame')
-rw-r--r--mash/wm/frame/caption_buttons/caption_button_types.h27
-rw-r--r--mash/wm/frame/caption_buttons/frame_caption_button.cc195
-rw-r--r--mash/wm/frame/caption_buttons/frame_caption_button.h102
-rw-r--r--mash/wm/frame/caption_buttons/frame_caption_button_container_view.cc353
-rw-r--r--mash/wm/frame/caption_buttons/frame_caption_button_container_view.h135
-rw-r--r--mash/wm/frame/default_header_painter.cc352
-rw-r--r--mash/wm/frame/default_header_painter.h122
-rw-r--r--mash/wm/frame/frame_border_hit_test_controller.cc82
-rw-r--r--mash/wm/frame/frame_border_hit_test_controller.h40
-rw-r--r--mash/wm/frame/header_painter.h52
-rw-r--r--mash/wm/frame/header_painter_util.cc80
-rw-r--r--mash/wm/frame/header_painter_util.h56
-rw-r--r--mash/wm/frame/move_loop.cc229
-rw-r--r--mash/wm/frame/move_loop.h121
-rw-r--r--mash/wm/frame/move_loop_unittest.cc185
-rw-r--r--mash/wm/frame/non_client_frame_view_mash.cc351
-rw-r--r--mash/wm/frame/non_client_frame_view_mash.h107
17 files changed, 2589 insertions, 0 deletions
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/frame/move_loop.cc b/mash/wm/frame/move_loop.cc
new file mode 100644
index 0000000..786ff76
--- /dev/null
+++ b/mash/wm/frame/move_loop.cc
@@ -0,0 +1,229 @@
+// 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/move_loop.h"
+
+#include "base/auto_reset.h"
+#include "components/mus/public/cpp/window.h"
+#include "components/mus/public/interfaces/input_event_constants.mojom.h"
+#include "mash/wm/property_util.h"
+#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) {
+ return gfx::ToFlooredPoint(gfx::PointF(event.pointer_data->location->x,
+ event.pointer_data->location->y));
+}
+
+gfx::Point EventScreenLocationToPoint(const mus::mojom::Event& event) {
+ return gfx::ToFlooredPoint(
+ gfx::PointF(event.pointer_data->location->screen_x,
+ event.pointer_data->location->screen_y));
+}
+
+mus::mojom::EventFlags MouseOnlyEventFlags(mus::mojom::EventFlags flags) {
+ return static_cast<mus::mojom::EventFlags>(
+ flags & (mus::mojom::EVENT_FLAGS_LEFT_MOUSE_BUTTON |
+ mus::mojom::EVENT_FLAGS_MIDDLE_MOUSE_BUTTON |
+ mus::mojom::EVENT_FLAGS_RIGHT_MOUSE_BUTTON));
+}
+
+gfx::Rect ClientAreaBounds(const mus::Window* window) {
+ gfx::Rect client_area(window->bounds().size());
+ client_area.Inset(window->client_area());
+ return client_area;
+}
+
+} // namespace
+
+MoveLoop::~MoveLoop() {
+ if (target_)
+ target_->RemoveObserver(this);
+}
+
+// static
+scoped_ptr<MoveLoop> MoveLoop::Create(mus::Window* target,
+ const mus::mojom::Event& event) {
+ DCHECK_EQ(event.action, mus::mojom::EVENT_TYPE_POINTER_DOWN);
+ const gfx::Point location(EventLocationToPoint(event));
+ if (!gfx::Rect(target->bounds().size()).Contains(location) ||
+ ClientAreaBounds(target).Contains(location)) {
+ return nullptr;
+ }
+
+ // Start a move on left mouse, or any other type of pointer.
+ if (event.pointer_data->kind == mus::mojom::POINTER_KIND_MOUSE &&
+ MouseOnlyEventFlags(event.flags) !=
+ mus::mojom::EVENT_FLAGS_LEFT_MOUSE_BUTTON) {
+ return nullptr;
+ }
+
+ Type type;
+ HorizontalLocation h_loc;
+ VerticalLocation v_loc;
+ DetermineType(target, location, &type, &h_loc, &v_loc);
+
+ return make_scoped_ptr(new MoveLoop(target, event, type, h_loc, v_loc));
+}
+
+MoveLoop::MoveResult MoveLoop::Move(const mus::mojom::Event& event) {
+ switch (event.action) {
+ case mus::mojom::EVENT_TYPE_POINTER_CANCEL:
+ if (event.pointer_data->pointer_id == pointer_id_) {
+ if (target_)
+ Revert();
+ return MoveResult::DONE;
+ }
+ return MoveResult::CONTINUE;
+
+ case mus::mojom::EVENT_TYPE_POINTER_MOVE:
+ if (target_ && event.pointer_data->pointer_id == pointer_id_)
+ MoveImpl(event);
+ return MoveResult::CONTINUE;
+
+ case mus::mojom::EVENT_TYPE_POINTER_UP:
+ if (event.pointer_data->pointer_id == pointer_id_) {
+ // TODO(sky): need to support changed_flags.
+ if (target_)
+ MoveImpl(event);
+ return MoveResult::DONE;
+ }
+ return MoveResult::CONTINUE;
+
+ default:
+ break;
+ }
+
+ return MoveResult::CONTINUE;
+}
+
+MoveLoop::MoveLoop(mus::Window* target,
+ const mus::mojom::Event& event,
+ Type type,
+ HorizontalLocation h_loc,
+ VerticalLocation v_loc)
+ : target_(target),
+ type_(type),
+ h_loc_(h_loc),
+ v_loc_(v_loc),
+ pointer_id_(event.pointer_data->pointer_id),
+ initial_event_screen_location_(EventScreenLocationToPoint(event)),
+ initial_window_bounds_(target->bounds()),
+ initial_user_set_bounds_(GetWindowUserSetBounds(target)),
+ changing_bounds_(false) {
+ target->AddObserver(this);
+}
+
+// static
+void MoveLoop::DetermineType(mus::Window* target,
+ const gfx::Point& location,
+ Type* type,
+ HorizontalLocation* h_loc,
+ VerticalLocation* v_loc) {
+ *h_loc = HorizontalLocation::OTHER;
+ *v_loc = VerticalLocation::OTHER;
+ const int resize_size = static_cast<int>(
+ kResizeSize *
+ std::max(1.f, target->viewport_metrics().device_pixel_ratio));
+
+ const gfx::Rect client_area(ClientAreaBounds(target));
+ if (location.x() < client_area.x())
+ *h_loc = HorizontalLocation::LEFT;
+ else if (location.x() >= client_area.right())
+ *h_loc = HorizontalLocation::RIGHT;
+ else
+ *h_loc = HorizontalLocation::OTHER;
+
+ if (location.y() < resize_size)
+ *v_loc = VerticalLocation::TOP;
+ else if (location.y() >= client_area.bottom())
+ *v_loc = VerticalLocation::BOTTOM;
+ else
+ *v_loc = VerticalLocation::OTHER;
+
+ if (*v_loc == VerticalLocation::OTHER && location.y() >= resize_size &&
+ *h_loc == HorizontalLocation::OTHER) {
+ *type = Type::MOVE;
+ return;
+ }
+ *type = Type::RESIZE;
+ DCHECK(*h_loc != HorizontalLocation::OTHER ||
+ *v_loc != VerticalLocation::OTHER);
+}
+
+void MoveLoop::MoveImpl(const mus::mojom::Event& event) {
+ const gfx::Vector2d delta =
+ EventScreenLocationToPoint(event) - initial_event_screen_location_;
+ const gfx::Rect new_bounds(DetermineBoundsFromDelta(delta));
+ base::AutoReset<bool> resetter(&changing_bounds_, true);
+ target_->SetBounds(new_bounds);
+ SetWindowUserSetBounds(target_, new_bounds);
+}
+
+void MoveLoop::Cancel() {
+ target_->RemoveObserver(this);
+ target_ = nullptr;
+}
+
+void MoveLoop::Revert() {
+ base::AutoReset<bool> resetter(&changing_bounds_, true);
+ target_->SetBounds(initial_window_bounds_);
+ SetWindowUserSetBounds(target_, initial_user_set_bounds_);
+}
+
+gfx::Rect MoveLoop::DetermineBoundsFromDelta(const gfx::Vector2d& delta) {
+ if (type_ == Type::MOVE) {
+ return gfx::Rect(initial_window_bounds_.origin() + delta,
+ initial_window_bounds_.size());
+ }
+
+ // TODO(sky): support better min sizes, make sure doesn't get bigger than
+ // screen and max. Also make sure keep some portion on screen.
+ gfx::Rect bounds(initial_window_bounds_);
+ if (h_loc_ == HorizontalLocation::LEFT) {
+ const int x = std::min(bounds.right() - 1, bounds.x() + delta.x());
+ const int width = bounds.right() - x;
+ bounds.set_x(x);
+ bounds.set_width(width);
+ } else if (h_loc_ == HorizontalLocation::RIGHT) {
+ bounds.set_width(std::max(1, bounds.width() + delta.x()));
+ }
+
+ if (v_loc_ == VerticalLocation::TOP) {
+ const int y = std::min(bounds.bottom() - 1, bounds.y() + delta.y());
+ const int height = bounds.bottom() - y;
+ bounds.set_y(y);
+ bounds.set_height(height);
+ } else if (v_loc_ == VerticalLocation::BOTTOM) {
+ bounds.set_height(std::max(1, bounds.height() + delta.y()));
+ }
+
+ return bounds;
+}
+
+void MoveLoop::OnTreeChanged(const TreeChangeParams& params) {
+ if (params.target == target_)
+ Cancel();
+}
+
+void MoveLoop::OnWindowBoundsChanged(mus::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ DCHECK_EQ(window, target_);
+ if (!changing_bounds_)
+ Cancel();
+}
+
+void MoveLoop::OnWindowVisibilityChanged(mus::Window* window) {
+ DCHECK_EQ(window, target_);
+ Cancel();
+}
+
+} // namespace wm
+} // namespace mash
diff --git a/mash/wm/frame/move_loop.h b/mash/wm/frame/move_loop.h
new file mode 100644
index 0000000..12ca46e
--- /dev/null
+++ b/mash/wm/frame/move_loop.h
@@ -0,0 +1,121 @@
+// 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_MOVE_LOOP_H_
+#define MASH_WM_FRAME_MOVE_LOOP_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/mus/public/cpp/window_observer.h"
+#include "components/mus/public/interfaces/input_events.mojom.h"
+#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:
+ enum MoveResult {
+ // The move is still ongoing.
+ CONTINUE,
+
+ // The move is done and the MoveLoop should be destroyed.
+ DONE,
+ };
+
+ enum class HorizontalLocation {
+ LEFT,
+ RIGHT,
+ OTHER,
+ };
+
+ enum class VerticalLocation {
+ TOP,
+ BOTTOM,
+ OTHER,
+ };
+
+ // Number of pixels in which a resize is triggered.
+ static const int kResizeSize = 8;
+
+ ~MoveLoop() override;
+
+ // If a move/resize loop should occur for the specified parameters creates
+ // and returns a new MoveLoop. All events should be funneled to the MoveLoop
+ // until done (Move()).
+ static scoped_ptr<MoveLoop> Create(mus::Window* target,
+ const mus::mojom::Event& event);
+
+ // Processes an event for a move/resize loop.
+ MoveResult Move(const mus::mojom::Event& event);
+
+ private:
+ enum class Type {
+ MOVE,
+ RESIZE,
+ };
+
+ MoveLoop(mus::Window* target,
+ const mus::mojom::Event& event,
+ Type type,
+ HorizontalLocation h_loc,
+ VerticalLocation v_loc);
+
+ static void DetermineType(mus::Window* target,
+ const gfx::Point& location,
+ Type* type,
+ HorizontalLocation* h_loc,
+ VerticalLocation* v_loc);
+
+ // Does the actual move/resize.
+ void MoveImpl(const mus::mojom::Event& event);
+
+ // Cancels the loop. This sets |target_| to null and removes the observer.
+ // After this the MoveLoop is still ongoing and won't stop until the
+ // appropriate event is received.
+ void Cancel();
+
+ void Revert();
+
+ gfx::Rect DetermineBoundsFromDelta(const gfx::Vector2d& delta);
+
+ // mus::WindowObserver:
+ void OnTreeChanged(const TreeChangeParams& params) override;
+ void OnWindowBoundsChanged(mus::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) override;
+ void OnWindowVisibilityChanged(mus::Window* window) override;
+
+ // The window this MoveLoop is acting on. |target_| is set to null if the
+ // window unexpectedly changes while the move is in progress.
+ mus::Window* target_;
+
+ const Type type_;
+ const HorizontalLocation h_loc_;
+ const VerticalLocation v_loc_;
+
+ // The id of the pointer that triggered the move.
+ const int32_t pointer_id_;
+
+ // Location of the event (in screen coordinates) that triggered the move.
+ const gfx::Point initial_event_screen_location_;
+
+ // Original bounds of the window.
+ const gfx::Rect initial_window_bounds_;
+
+ const gfx::Rect initial_user_set_bounds_;
+
+ // Set to true when MoveLoop changes the bounds of |target_|. The move is
+ // canceled if the bounds change unexpectedly during the move.
+ bool changing_bounds_;
+
+ DISALLOW_COPY_AND_ASSIGN(MoveLoop);
+};
+
+} // namespace wm
+} // namespace mash
+
+#endif // MASH_WM_FRAME_MOVE_LOOP_H_
diff --git a/mash/wm/frame/move_loop_unittest.cc b/mash/wm/frame/move_loop_unittest.cc
new file mode 100644
index 0000000..86ca32c
--- /dev/null
+++ b/mash/wm/frame/move_loop_unittest.cc
@@ -0,0 +1,185 @@
+// 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/move_loop.h"
+
+#include "components/mus/public/cpp/tests/test_window.h"
+#include "mojo/converters/input_events/input_events_type_converters.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event.h"
+#include "ui/gfx/geometry/point.h"
+#include "ui/gfx/geometry/rect.h"
+
+using MoveLoopTest = testing::Test;
+
+namespace mash {
+namespace wm {
+
+namespace {
+
+// Sets the client area for |window|. Padding is provided on the top so that
+// there is a movable region. All other edges are sized to
+// |MoveLoop::kResizeSize|.
+void SetClientArea(mus::Window* window) {
+ if (window->bounds().IsEmpty())
+ window->SetBounds(gfx::Rect(100, 200, 300, 400));
+
+ window->SetClientArea(
+ gfx::Insets(MoveLoop::kResizeSize + 10, MoveLoop::kResizeSize,
+ MoveLoop::kResizeSize, MoveLoop::kResizeSize));
+}
+
+mus::mojom::EventPtr CreatePointerDownEvent(const gfx::Point& location) {
+ const ui::TouchEvent event(ui::ET_TOUCH_PRESSED, location, 1,
+ base::TimeDelta());
+ return mus::mojom::Event::From(static_cast<const ui::Event&>(event));
+}
+
+mus::mojom::EventPtr CreatePointerMove(const gfx::Point& location) {
+ const ui::TouchEvent event(ui::ET_TOUCH_MOVED, location, 1,
+ base::TimeDelta());
+ return mus::mojom::Event::From(static_cast<const ui::Event&>(event));
+}
+
+enum class HorizontalLocation {
+ LEFT,
+ MIDDLE,
+ RIGHT,
+};
+
+enum class VerticalLocation {
+ TOP_MOVE,
+ TOP_RESIZE,
+ MIDDLE,
+ BOTTOM,
+};
+
+int HorizontalLocationToPoint(const mus::Window* window,
+ HorizontalLocation loc) {
+ if (loc == HorizontalLocation::LEFT)
+ return 0;
+ return loc == HorizontalLocation::MIDDLE ? window->bounds().width() / 2
+ : window->bounds().width() - 1;
+}
+
+int VerticalLocationToPoint(const mus::Window* window, VerticalLocation loc) {
+ switch (loc) {
+ case VerticalLocation::TOP_MOVE:
+ return MoveLoop::kResizeSize + 1;
+ case VerticalLocation::TOP_RESIZE:
+ return 0;
+ case VerticalLocation::MIDDLE:
+ return window->bounds().height() / 2;
+ case VerticalLocation::BOTTOM:
+ return window->bounds().height() - 1;
+ }
+ return 0;
+}
+
+gfx::Point LocationToPoint(const mus::Window* window,
+ HorizontalLocation h_loc,
+ VerticalLocation v_loc) {
+ return gfx::Point(HorizontalLocationToPoint(window, h_loc),
+ VerticalLocationToPoint(window, v_loc));
+}
+
+} // namespace
+
+TEST_F(MoveLoopTest, Move) {
+ struct TestData {
+ HorizontalLocation initial_h_loc;
+ VerticalLocation initial_v_loc;
+ // Number of values in delta_* that are valid. For example, if this is 1
+ // then only one move is generated with thye point |delta_x[0]|,
+ // |delta_y[0]|.
+ int delta_count;
+ int delta_x[3];
+ int delta_y[3];
+ gfx::Rect expected_bounds;
+ } data[] = {
+ // Move.
+ {HorizontalLocation::MIDDLE,
+ VerticalLocation::TOP_MOVE,
+ 1,
+ {1, 0, 0},
+ {2, 0, 0},
+ gfx::Rect(101, 202, 300, 400)},
+
+ // Resize from top, left.
+ {HorizontalLocation::LEFT,
+ VerticalLocation::TOP_RESIZE,
+ 1,
+ {1, 0, 0},
+ {2, 0, 0},
+ gfx::Rect(101, 202, 299, 398)},
+
+ // Resize from left.
+ {HorizontalLocation::LEFT,
+ VerticalLocation::MIDDLE,
+ 1,
+ {1, 0, 0},
+ {2, 0, 0},
+ gfx::Rect(101, 200, 299, 400)},
+
+ // Resize from bottom, left.
+ {HorizontalLocation::LEFT,
+ VerticalLocation::BOTTOM,
+ 1,
+ {-1, 0, 0},
+ {-2, 0, 0},
+ gfx::Rect(99, 200, 301, 398)},
+
+ // Resize from bottom.
+ {HorizontalLocation::MIDDLE,
+ VerticalLocation::BOTTOM,
+ 1,
+ {-1, 0, 0},
+ {4, 0, 0},
+ gfx::Rect(100, 200, 300, 404)},
+
+ // Resize from bottom, right.
+ {HorizontalLocation::RIGHT,
+ VerticalLocation::BOTTOM,
+ 1,
+ {-3, 0, 0},
+ {4, 0, 0},
+ gfx::Rect(100, 200, 297, 404)},
+
+ // Resize from right.
+ {HorizontalLocation::RIGHT,
+ VerticalLocation::MIDDLE,
+ 1,
+ {6, 0, 0},
+ {-4, 0, 0},
+ gfx::Rect(100, 200, 306, 400)},
+
+ // Resize from top, right.
+ {HorizontalLocation::RIGHT,
+ VerticalLocation::TOP_RESIZE,
+ 1,
+ {6, 0, 0},
+ {5, 0, 0},
+ gfx::Rect(100, 205, 306, 395)},
+ };
+
+ for (size_t i = 0; i < arraysize(data); ++i) {
+ mus::TestWindow window;
+ SetClientArea(&window);
+ gfx::Point pointer_location(
+ LocationToPoint(&window, data[i].initial_h_loc, data[i].initial_v_loc));
+ scoped_ptr<MoveLoop> move_loop =
+ MoveLoop::Create(&window, *CreatePointerDownEvent(pointer_location));
+ ASSERT_TRUE(move_loop.get()) << i;
+ for (int j = 0; j < data[i].delta_count; ++j) {
+ pointer_location.Offset(data[i].delta_x[j], data[i].delta_y[j]);
+ ASSERT_EQ(MoveLoop::MoveResult::CONTINUE,
+ move_loop->Move(*CreatePointerMove(pointer_location)))
+ << i << " " << j;
+ }
+ 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_