1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ash/wm/custom_frame_view_ash.h"
7 #include "ash/ash_switches.h"
8 #include "ash/wm/caption_buttons/frame_caption_button_container_view.h"
9 #include "ash/wm/caption_buttons/frame_maximize_button.h"
10 #include "ash/wm/caption_buttons/frame_maximize_button_observer.h"
11 #include "ash/wm/frame_border_hit_test_controller.h"
12 #include "ash/wm/header_painter.h"
13 #include "ash/wm/immersive_fullscreen_controller.h"
14 #include "ash/wm/window_state.h"
15 #include "ash/wm/window_state_delegate.h"
16 #include "ash/wm/window_state_observer.h"
17 #include "base/command_line.h"
18 #include "base/debug/leak_annotations.h"
19 #include "grit/ash_resources.h"
20 #include "ui/aura/client/aura_constants.h"
21 #include "ui/aura/window.h"
22 #include "ui/aura/window_observer.h"
23 #include "ui/gfx/canvas.h"
24 #include "ui/gfx/font_list.h"
25 #include "ui/gfx/rect.h"
26 #include "ui/gfx/rect_conversions.h"
27 #include "ui/gfx/size.h"
28 #include "ui/views/view.h"
29 #include "ui/views/widget/native_widget_aura.h"
30 #include "ui/views/widget/widget.h"
31 #include "ui/views/widget/widget_delegate.h"
32 #include "ui/views/widget/widget_deletion_observer.h"
36 const gfx::FontList& GetTitleFontList() {
37 static const gfx::FontList* title_font_list =
38 new gfx::FontList(views::NativeWidgetAura::GetWindowTitleFontList());
39 ANNOTATE_LEAKING_OBJECT_PTR(title_font_list);
40 return *title_font_list;
43 ///////////////////////////////////////////////////////////////////////////////
44 // CustomFrameViewAshWindowStateDelegate
46 // Handles a user's fullscreen request (Shift+F4/F4). Puts the window into
47 // immersive fullscreen if immersive fullscreen is enabled for non-browser
49 class CustomFrameViewAshWindowStateDelegate
50 : public ash::wm::WindowStateDelegate,
51 public ash::wm::WindowStateObserver,
52 public aura::WindowObserver {
54 CustomFrameViewAshWindowStateDelegate(
55 ash::wm::WindowState* window_state,
56 ash::CustomFrameViewAsh* custom_frame_view)
57 : window_state_(NULL) {
58 #if defined(OS_CHROMEOS)
59 // TODO(pkotwicz): Investigate if immersive fullscreen can be enabled for
61 if (ash::switches::UseImmersiveFullscreenForAllWindows()) {
62 immersive_fullscreen_controller_.reset(
63 new ash::ImmersiveFullscreenController);
64 custom_frame_view->InitImmersiveFullscreenControllerForView(
65 immersive_fullscreen_controller_.get());
67 // Add a window state observer to exit fullscreen properly in case
68 // fullscreen is exited without going through
69 // WindowState::ToggleFullscreen(). This is the case when exiting
70 // immersive fullscreen via the "Restore" window control.
71 // TODO(pkotwicz): This is a hack. Remove ASAP. http://crbug.com/319048
72 window_state_ = window_state;
73 window_state_->AddObserver(this);
74 window_state_->window()->AddObserver(this);
78 virtual ~CustomFrameViewAshWindowStateDelegate() {
80 window_state_->RemoveObserver(this);
81 window_state_->window()->RemoveObserver(this);
85 // Overridden from ash::wm::WindowStateDelegate:
86 virtual bool ToggleFullscreen(ash::wm::WindowState* window_state) OVERRIDE {
87 bool enter_fullscreen = !window_state->IsFullscreen();
88 if (enter_fullscreen) {
89 window_state->window()->SetProperty(aura::client::kShowStateKey,
90 ui::SHOW_STATE_FULLSCREEN);
92 window_state->Restore();
94 if (immersive_fullscreen_controller_) {
95 immersive_fullscreen_controller_->SetEnabled(
96 ash::ImmersiveFullscreenController::WINDOW_TYPE_OTHER,
101 // Overridden from aura::WindowObserver:
102 virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {
103 window_state_->RemoveObserver(this);
104 window_state_->window()->RemoveObserver(this);
105 window_state_ = NULL;
107 // Overridden from ash::wm::WindowStateObserver:
108 virtual void OnPostWindowShowTypeChange(
109 ash::wm::WindowState* window_state,
110 ash::wm::WindowShowType old_type) OVERRIDE {
111 if (!window_state->IsFullscreen() &&
112 !window_state->IsMinimized() &&
113 immersive_fullscreen_controller_.get() &&
114 immersive_fullscreen_controller_->IsEnabled()) {
115 immersive_fullscreen_controller_->SetEnabled(
116 ash::ImmersiveFullscreenController::WINDOW_TYPE_OTHER,
121 ash::wm::WindowState* window_state_;
122 scoped_ptr<ash::ImmersiveFullscreenController>
123 immersive_fullscreen_controller_;
125 DISALLOW_COPY_AND_ASSIGN(CustomFrameViewAshWindowStateDelegate);
132 ///////////////////////////////////////////////////////////////////////////////
133 // CustomFrameViewAsh::HeaderView
135 // View which paints the header. It slides off and on screen in immersive
137 class CustomFrameViewAsh::HeaderView
138 : public views::View,
139 public ImmersiveFullscreenController::Delegate,
140 public FrameMaximizeButtonObserver {
142 // |frame| is the widget that the caption buttons act on.
143 explicit HeaderView(views::Widget* frame);
144 virtual ~HeaderView();
146 // Schedules a repaint for the entire title.
147 void SchedulePaintForTitle();
149 // Tells the window controls to reset themselves to the normal state.
150 void ResetWindowControls();
152 // Returns the amount of the view's pixels which should be on screen.
153 int GetPreferredOnScreenHeight() const;
155 // Returns the view's preferred height.
156 int GetPreferredHeight() const;
158 // Returns the view's minimum width.
159 int GetMinimumWidth() const;
161 // views::View overrides:
162 virtual void Layout() OVERRIDE;
163 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
165 HeaderPainter* header_painter() {
166 return header_painter_.get();
170 // ImmersiveFullscreenController::Delegate overrides:
171 virtual void OnImmersiveRevealStarted() OVERRIDE;
172 virtual void OnImmersiveRevealEnded() OVERRIDE;
173 virtual void OnImmersiveFullscreenExited() OVERRIDE;
174 virtual void SetVisibleFraction(double visible_fraction) OVERRIDE;
175 virtual std::vector<gfx::Rect> GetVisibleBoundsInScreen() const OVERRIDE;
177 // FrameMaximizeButtonObserver overrides:
178 virtual void OnMaximizeBubbleShown(views::Widget* bubble) OVERRIDE;
180 // The widget that the caption buttons act on.
181 views::Widget* frame_;
183 // Helper for painting the header.
184 scoped_ptr<HeaderPainter> header_painter_;
186 // View which contains the window caption buttons.
187 FrameCaptionButtonContainerView* caption_button_container_;
189 // The maximize bubble widget. |maximize_bubble_| may be non-NULL but have
190 // been already destroyed.
191 views::Widget* maximize_bubble_;
193 // Keeps track of whether |maximize_bubble_| is still alive.
194 scoped_ptr<views::WidgetDeletionObserver> maximize_bubble_lifetime_observer_;
196 // The fraction of the header's height which is visible while in fullscreen.
197 // This value is meaningless when not in fullscreen.
198 double fullscreen_visible_fraction_;
200 DISALLOW_COPY_AND_ASSIGN(HeaderView);
203 CustomFrameViewAsh::HeaderView::HeaderView(views::Widget* frame)
205 header_painter_(new ash::HeaderPainter),
206 caption_button_container_(NULL),
207 maximize_bubble_(NULL),
208 fullscreen_visible_fraction_(0) {
209 // Unfortunately, there is no views::WidgetDelegate::CanMinimize(). Assume
210 // that the window frame can be minimized if it can be maximized.
211 FrameCaptionButtonContainerView::MinimizeAllowed minimize_allowed =
212 frame_->widget_delegate()->CanMaximize() ?
213 FrameCaptionButtonContainerView::MINIMIZE_ALLOWED :
214 FrameCaptionButtonContainerView::MINIMIZE_DISALLOWED;
215 caption_button_container_ = new FrameCaptionButtonContainerView(frame_,
217 AddChildView(caption_button_container_);
218 FrameMaximizeButton* frame_maximize_button =
219 caption_button_container_->GetOldStyleSizeButton();
220 if (frame_maximize_button)
221 frame_maximize_button->AddObserver(this);
223 header_painter_->Init(frame_, this, NULL, caption_button_container_);
226 CustomFrameViewAsh::HeaderView::~HeaderView() {
227 FrameMaximizeButton* frame_maximize_button =
228 caption_button_container_->GetOldStyleSizeButton();
229 if (frame_maximize_button)
230 frame_maximize_button->RemoveObserver(this);
233 void CustomFrameViewAsh::HeaderView::SchedulePaintForTitle() {
234 header_painter_->SchedulePaintForTitle(GetTitleFontList());
237 void CustomFrameViewAsh::HeaderView::ResetWindowControls() {
238 caption_button_container_->ResetWindowControls();
241 int CustomFrameViewAsh::HeaderView::GetPreferredOnScreenHeight() const {
242 if (frame_->IsFullscreen()) {
243 return static_cast<int>(
244 GetPreferredHeight() * fullscreen_visible_fraction_);
246 return GetPreferredHeight();
249 int CustomFrameViewAsh::HeaderView::GetPreferredHeight() const {
250 // Reserve enough space to see the buttons and the separator line.
251 return caption_button_container_->bounds().bottom() +
252 header_painter_->HeaderContentSeparatorSize();
255 int CustomFrameViewAsh::HeaderView::GetMinimumWidth() const {
256 return header_painter_->GetMinimumHeaderWidth();
259 void CustomFrameViewAsh::HeaderView::Layout() {
260 header_painter_->LayoutHeader(true);
261 header_painter_->set_header_height(GetPreferredHeight());
264 void CustomFrameViewAsh::HeaderView::OnPaint(gfx::Canvas* canvas) {
265 int theme_image_id = 0;
266 if (frame_->IsMaximized() || frame_->IsFullscreen())
267 theme_image_id = IDR_AURA_WINDOW_HEADER_BASE_MINIMAL;
268 else if (frame_->non_client_view()->frame_view()->ShouldPaintAsActive())
269 theme_image_id = IDR_AURA_WINDOW_HEADER_BASE_ACTIVE;
271 theme_image_id = IDR_AURA_WINDOW_HEADER_BASE_INACTIVE;
273 header_painter_->PaintHeader(
277 header_painter_->PaintTitleBar(canvas, GetTitleFontList());
278 header_painter_->PaintHeaderContentSeparator(canvas);
281 void CustomFrameViewAsh::HeaderView::OnImmersiveRevealStarted() {
282 fullscreen_visible_fraction_ = 0;
283 SetPaintToLayer(true);
287 void CustomFrameViewAsh::HeaderView::OnImmersiveRevealEnded() {
288 fullscreen_visible_fraction_ = 0;
289 SetPaintToLayer(false);
293 void CustomFrameViewAsh::HeaderView::OnImmersiveFullscreenExited() {
294 fullscreen_visible_fraction_ = 0;
295 SetPaintToLayer(false);
299 void CustomFrameViewAsh::HeaderView::SetVisibleFraction(
300 double visible_fraction) {
301 if (fullscreen_visible_fraction_ != visible_fraction) {
302 fullscreen_visible_fraction_ = visible_fraction;
307 std::vector<gfx::Rect>
308 CustomFrameViewAsh::HeaderView::GetVisibleBoundsInScreen() const {
309 // TODO(pkotwicz): Implement views::View::ConvertRectToScreen().
310 gfx::Rect visible_bounds(GetVisibleBounds());
311 gfx::Point visible_origin_in_screen(visible_bounds.origin());
312 views::View::ConvertPointToScreen(this, &visible_origin_in_screen);
313 std::vector<gfx::Rect> bounds_in_screen;
314 bounds_in_screen.push_back(
315 gfx::Rect(visible_origin_in_screen, visible_bounds.size()));
316 if (maximize_bubble_lifetime_observer_.get() &&
317 maximize_bubble_lifetime_observer_->IsWidgetAlive()) {
318 bounds_in_screen.push_back(maximize_bubble_->GetWindowBoundsInScreen());
320 return bounds_in_screen;
323 void CustomFrameViewAsh::HeaderView::OnMaximizeBubbleShown(
324 views::Widget* bubble) {
325 maximize_bubble_ = bubble;
326 maximize_bubble_lifetime_observer_.reset(
327 new views::WidgetDeletionObserver(bubble));
330 ///////////////////////////////////////////////////////////////////////////////
331 // CustomFrameViewAsh::OverlayView
333 // View which takes up the entire widget and contains the HeaderView. HeaderView
334 // is a child of OverlayView to avoid creating a larger texture than necessary
335 // when painting the HeaderView to its own layer.
336 class CustomFrameViewAsh::OverlayView : public views::View {
338 explicit OverlayView(HeaderView* header_view);
339 virtual ~OverlayView();
341 // views::View override:
342 virtual void Layout() OVERRIDE;
343 virtual bool HitTestRect(const gfx::Rect& rect) const OVERRIDE;
346 HeaderView* header_view_;
348 DISALLOW_COPY_AND_ASSIGN(OverlayView);
351 CustomFrameViewAsh::OverlayView::OverlayView(HeaderView* header_view)
352 : header_view_(header_view) {
353 AddChildView(header_view);
356 CustomFrameViewAsh::OverlayView::~OverlayView() {
359 void CustomFrameViewAsh::OverlayView::Layout() {
360 // Layout |header_view_| because layout affects the result of
361 // GetPreferredOnScreenHeight().
362 header_view_->Layout();
364 int onscreen_height = header_view_->GetPreferredOnScreenHeight();
365 if (onscreen_height == 0) {
366 header_view_->SetVisible(false);
368 int height = header_view_->GetPreferredHeight();
369 header_view_->SetBounds(0, onscreen_height - height, width(), height);
370 header_view_->SetVisible(true);
374 bool CustomFrameViewAsh::OverlayView::HitTestRect(const gfx::Rect& rect) const {
375 // Grab events in the header view. Return false for other events so that they
376 // can be handled by the client view.
377 return header_view_->HitTestRect(rect);
380 ////////////////////////////////////////////////////////////////////////////////
381 // CustomFrameViewAsh, public:
384 const char CustomFrameViewAsh::kViewClassName[] = "CustomFrameViewAsh";
386 CustomFrameViewAsh::CustomFrameViewAsh(views::Widget* frame)
388 header_view_(new HeaderView(frame)),
389 frame_border_hit_test_controller_(
390 new FrameBorderHitTestController(frame_)) {
391 // |header_view_| is set as the non client view's overlay view so that it can
392 // overlay the web contents in immersive fullscreen.
393 frame->non_client_view()->SetOverlayView(new OverlayView(header_view_));
395 // A delegate for a more complex way of fullscreening the window may already
396 // be set. This is the case for packaged apps.
397 wm::WindowState* window_state = wm::GetWindowState(frame->GetNativeWindow());
398 if (!window_state->HasDelegate()) {
399 window_state->SetDelegate(scoped_ptr<wm::WindowStateDelegate>(
400 new CustomFrameViewAshWindowStateDelegate(
401 window_state, this)));
405 CustomFrameViewAsh::~CustomFrameViewAsh() {
408 void CustomFrameViewAsh::InitImmersiveFullscreenControllerForView(
409 ImmersiveFullscreenController* immersive_fullscreen_controller) {
410 immersive_fullscreen_controller->Init(header_view_, frame_, header_view_);
413 ////////////////////////////////////////////////////////////////////////////////
414 // CustomFrameViewAsh, views::NonClientFrameView overrides:
416 gfx::Rect CustomFrameViewAsh::GetBoundsForClientView() const {
417 int top_height = NonClientTopBorderHeight();
418 return HeaderPainter::GetBoundsForClientView(top_height, bounds());
421 gfx::Rect CustomFrameViewAsh::GetWindowBoundsForClientBounds(
422 const gfx::Rect& client_bounds) const {
423 int top_height = NonClientTopBorderHeight();
424 return HeaderPainter::GetWindowBoundsForClientBounds(top_height,
428 int CustomFrameViewAsh::NonClientHitTest(const gfx::Point& point) {
429 return FrameBorderHitTestController::NonClientHitTest(this,
430 header_view_->header_painter(), point);
433 void CustomFrameViewAsh::GetWindowMask(const gfx::Size& size,
434 gfx::Path* window_mask) {
435 // No window masks in Aura.
438 void CustomFrameViewAsh::ResetWindowControls() {
439 header_view_->ResetWindowControls();
442 void CustomFrameViewAsh::UpdateWindowIcon() {
445 void CustomFrameViewAsh::UpdateWindowTitle() {
446 header_view_->SchedulePaintForTitle();
449 ////////////////////////////////////////////////////////////////////////////////
450 // CustomFrameViewAsh, views::View overrides:
452 gfx::Size CustomFrameViewAsh::GetPreferredSize() {
453 gfx::Size pref = frame_->client_view()->GetPreferredSize();
454 gfx::Rect bounds(0, 0, pref.width(), pref.height());
455 return frame_->non_client_view()->GetWindowBoundsForClientBounds(
459 const char* CustomFrameViewAsh::GetClassName() const {
460 return kViewClassName;
463 gfx::Size CustomFrameViewAsh::GetMinimumSize() {
464 gfx::Size min_client_view_size(frame_->client_view()->GetMinimumSize());
466 std::max(header_view_->GetMinimumWidth(), min_client_view_size.width()),
467 NonClientTopBorderHeight() + min_client_view_size.height());
470 gfx::Size CustomFrameViewAsh::GetMaximumSize() {
471 return frame_->client_view()->GetMaximumSize();
474 void CustomFrameViewAsh::SchedulePaintInRect(const gfx::Rect& r) {
475 // We may end up here before |header_view_| has been added to the Widget.
476 if (header_view_->GetWidget()) {
477 // The HeaderView is not a child of CustomFrameViewAsh. Redirect the paint
478 // to HeaderView instead.
479 gfx::RectF to_paint(r);
480 views::View::ConvertRectToTarget(this, header_view_, &to_paint);
481 header_view_->SchedulePaintInRect(gfx::ToEnclosingRect(to_paint));
483 views::NonClientFrameView::SchedulePaintInRect(r);
487 bool CustomFrameViewAsh::HitTestRect(const gfx::Rect& rect) const {
488 // NonClientView hit tests the NonClientFrameView first instead of going in
489 // z-order. Return false so that events get to the OverlayView.
493 views::View* CustomFrameViewAsh::GetHeaderView() {
497 ////////////////////////////////////////////////////////////////////////////////
498 // CustomFrameViewAsh, private:
500 int CustomFrameViewAsh::NonClientTopBorderHeight() const {
501 return frame_->IsFullscreen() ? 0 : header_view_->GetPreferredHeight();