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.
4
5 #include "ash/wm/custom_frame_view_ash.h"
6
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 "base/command_line.h"
17 #include "grit/ash_resources.h"
18 #include "ui/aura/client/aura_constants.h"
19 #include "ui/aura/window.h"
20 #include "ui/aura/window_observer.h"
21 #include "ui/gfx/canvas.h"
22 #include "ui/gfx/font.h"
23 #include "ui/gfx/rect.h"
24 #include "ui/gfx/size.h"
25 #include "ui/views/view.h"
26 #include "ui/views/widget/native_widget_aura.h"
27 #include "ui/views/widget/widget.h"
28 #include "ui/views/widget/widget_delegate.h"
29 #include "ui/views/widget/widget_deletion_observer.h"
30
31 namespace {
32
GetTitleFont()33 const gfx::Font& GetTitleFont() {
34 static gfx::Font* title_font = NULL;
35 if (!title_font)
36 title_font = new gfx::Font(views::NativeWidgetAura::GetWindowTitleFont());
37 return *title_font;
38 }
39
40 ///////////////////////////////////////////////////////////////////////////////
41 // CustomFrameViewAshWindowStateDelegate
42
43 // Handles a user's fullscreen request (Shift+F4/F4). Puts the window into
44 // immersive fullscreen if the kAshEnableImmersiveFullscreenForAllWindows
45 // flag is set.
46 class CustomFrameViewAshWindowStateDelegate
47 : public ash::wm::WindowStateDelegate,
48 public ash::wm::WindowStateObserver,
49 public aura::WindowObserver {
50 public:
CustomFrameViewAshWindowStateDelegate(ash::wm::WindowState * window_state,ash::CustomFrameViewAsh * custom_frame_view)51 CustomFrameViewAshWindowStateDelegate(
52 ash::wm::WindowState* window_state,
53 ash::CustomFrameViewAsh* custom_frame_view)
54 : window_state_(NULL) {
55 #if defined(OS_CHROMEOS)
56 // TODO(pkotwicz): Investigate if immersive fullscreen can be enabled for
57 // Windows Ash.
58 if (CommandLine::ForCurrentProcess()->HasSwitch(
59 ash::switches::kAshEnableImmersiveFullscreenForAllWindows)) {
60 immersive_fullscreen_controller_.reset(
61 new ash::ImmersiveFullscreenController);
62 custom_frame_view->InitImmersiveFullscreenControllerForView(
63 immersive_fullscreen_controller_.get());
64
65 // Add a window state observer to exit fullscreen properly in case
66 // fullscreen is exited without going through
67 // WindowState::ToggleFullscreen(). This is the case when exiting
68 // immersive fullscreen via the "Restore" window control.
69 // TODO(pkotwicz): This is a hack. Remove ASAP. http://crbug.com/319048
70 window_state_ = window_state;
71 window_state_->AddObserver(this);
72 window_state_->window()->AddObserver(this);
73 }
74 #endif
75 }
~CustomFrameViewAshWindowStateDelegate()76 virtual ~CustomFrameViewAshWindowStateDelegate() {
77 if (window_state_) {
78 window_state_->RemoveObserver(this);
79 window_state_->window()->RemoveObserver(this);
80 }
81 }
82 private:
83 // Overridden from ash::wm::WindowStateDelegate:
ToggleFullscreen(ash::wm::WindowState * window_state)84 virtual bool ToggleFullscreen(ash::wm::WindowState* window_state) OVERRIDE {
85 bool enter_fullscreen = !window_state->IsFullscreen();
86 if (enter_fullscreen) {
87 window_state->window()->SetProperty(aura::client::kShowStateKey,
88 ui::SHOW_STATE_FULLSCREEN);
89 } else {
90 window_state->Restore();
91 }
92 if (immersive_fullscreen_controller_) {
93 immersive_fullscreen_controller_->SetEnabled(
94 ash::ImmersiveFullscreenController::WINDOW_TYPE_OTHER,
95 enter_fullscreen);
96 }
97 return true;
98 }
99 // Overridden from aura::WindowObserver:
OnWindowDestroying(aura::Window * window)100 virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {
101 window_state_->RemoveObserver(this);
102 window_state_->window()->RemoveObserver(this);
103 window_state_ = NULL;
104 }
105 // Overridden from ash::wm::WindowStateObserver:
OnWindowShowTypeChanged(ash::wm::WindowState * window_state,ash::wm::WindowShowType old_type)106 virtual void OnWindowShowTypeChanged(
107 ash::wm::WindowState* window_state,
108 ash::wm::WindowShowType old_type) OVERRIDE {
109 if (!window_state->IsFullscreen() &&
110 !window_state->IsMinimized() &&
111 immersive_fullscreen_controller_.get() &&
112 immersive_fullscreen_controller_->IsEnabled()) {
113 immersive_fullscreen_controller_->SetEnabled(
114 ash::ImmersiveFullscreenController::WINDOW_TYPE_OTHER,
115 false);
116 }
117 }
118
119 ash::wm::WindowState* window_state_;
120 scoped_ptr<ash::ImmersiveFullscreenController>
121 immersive_fullscreen_controller_;
122
123 DISALLOW_COPY_AND_ASSIGN(CustomFrameViewAshWindowStateDelegate);
124 };
125
126 } // namespace
127
128 namespace ash {
129
130 ///////////////////////////////////////////////////////////////////////////////
131 // CustomFrameViewAsh::HeaderView
132
133 // View which paints the header. It slides off and on screen in immersive
134 // fullscreen.
135 class CustomFrameViewAsh::HeaderView
136 : public views::View,
137 public ImmersiveFullscreenController::Delegate,
138 public FrameMaximizeButtonObserver {
139 public:
140 // |frame| is the widget that the caption buttons act on.
141 explicit HeaderView(views::Widget* frame);
142 virtual ~HeaderView();
143
144 // Schedules a repaint for the entire title.
145 void SchedulePaintForTitle();
146
147 // Tells the window controls to reset themselves to the normal state.
148 void ResetWindowControls();
149
150 // Returns the amount of the view's pixels which should be on screen.
151 int GetPreferredOnScreenHeight() const;
152
153 // Returns the view's preferred height.
154 int GetPreferredHeight() const;
155
156 // Returns the view's minimum width.
157 int GetMinimumWidth() const;
158
159 // views::View overrides:
160 virtual void Layout() OVERRIDE;
161 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
162
163 // Sets whether the header should be painted as active.
set_paint_as_active(bool paint_as_active)164 void set_paint_as_active(bool paint_as_active) {
165 paint_as_active_ = paint_as_active;
166 }
167
header_painter()168 HeaderPainter* header_painter() {
169 return header_painter_.get();
170 }
171
172 private:
173 // ImmersiveFullscreenController::Delegate overrides:
174 virtual void OnImmersiveRevealStarted() OVERRIDE;
175 virtual void OnImmersiveRevealEnded() OVERRIDE;
176 virtual void OnImmersiveFullscreenExited() OVERRIDE;
177 virtual void SetVisibleFraction(double visible_fraction) OVERRIDE;
178 virtual std::vector<gfx::Rect> GetVisibleBoundsInScreen() const OVERRIDE;
179
180 // FrameMaximizeButtonObserver overrides:
181 virtual void OnMaximizeBubbleShown(views::Widget* bubble) OVERRIDE;
182
183 // The widget that the caption buttons act on.
184 views::Widget* frame_;
185
186 // Helper for painting the header.
187 scoped_ptr<HeaderPainter> header_painter_;
188
189 // View which contains the window caption buttons.
190 FrameCaptionButtonContainerView* caption_button_container_;
191
192 // The maximize bubble widget. |maximize_bubble_| may be non-NULL but have
193 // been already destroyed.
194 views::Widget* maximize_bubble_;
195
196 // Keeps track of whether |maximize_bubble_| is still alive.
197 scoped_ptr<views::WidgetDeletionObserver> maximize_bubble_lifetime_observer_;
198
199 // Whether the header should be painted as active.
200 bool paint_as_active_;
201
202 // The fraction of the header's height which is visible while in fullscreen.
203 // This value is meaningless when not in fullscreen.
204 double fullscreen_visible_fraction_;
205
206 DISALLOW_COPY_AND_ASSIGN(HeaderView);
207 };
208
HeaderView(views::Widget * frame)209 CustomFrameViewAsh::HeaderView::HeaderView(views::Widget* frame)
210 : frame_(frame),
211 header_painter_(new ash::HeaderPainter),
212 caption_button_container_(NULL),
213 maximize_bubble_(NULL),
214 paint_as_active_(false),
215 fullscreen_visible_fraction_(0) {
216 // Unfortunately, there is no views::WidgetDelegate::CanMinimize(). Assume
217 // that the window frame can be minimized if it can be maximized.
218 FrameCaptionButtonContainerView::MinimizeAllowed minimize_allowed =
219 frame_->widget_delegate()->CanMaximize() ?
220 FrameCaptionButtonContainerView::MINIMIZE_ALLOWED :
221 FrameCaptionButtonContainerView::MINIMIZE_DISALLOWED;
222 caption_button_container_ = new FrameCaptionButtonContainerView(frame_,
223 minimize_allowed);
224 AddChildView(caption_button_container_);
225 FrameMaximizeButton* frame_maximize_button =
226 caption_button_container_->GetOldStyleSizeButton();
227 if (frame_maximize_button)
228 frame_maximize_button->AddObserver(this);
229
230 header_painter_->Init(frame_, this, NULL, caption_button_container_);
231 }
232
~HeaderView()233 CustomFrameViewAsh::HeaderView::~HeaderView() {
234 FrameMaximizeButton* frame_maximize_button =
235 caption_button_container_->GetOldStyleSizeButton();
236 if (frame_maximize_button)
237 frame_maximize_button->RemoveObserver(this);
238 }
239
SchedulePaintForTitle()240 void CustomFrameViewAsh::HeaderView::SchedulePaintForTitle() {
241 header_painter_->SchedulePaintForTitle(GetTitleFont());
242 }
243
ResetWindowControls()244 void CustomFrameViewAsh::HeaderView::ResetWindowControls() {
245 caption_button_container_->ResetWindowControls();
246 }
247
GetPreferredOnScreenHeight() const248 int CustomFrameViewAsh::HeaderView::GetPreferredOnScreenHeight() const {
249 if (frame_->IsFullscreen()) {
250 return static_cast<int>(
251 GetPreferredHeight() * fullscreen_visible_fraction_);
252 }
253 return GetPreferredHeight();
254 }
255
GetPreferredHeight() const256 int CustomFrameViewAsh::HeaderView::GetPreferredHeight() const {
257 // Reserve enough space to see the buttons and the separator line.
258 return caption_button_container_->bounds().bottom() +
259 header_painter_->HeaderContentSeparatorSize();
260 }
261
GetMinimumWidth() const262 int CustomFrameViewAsh::HeaderView::GetMinimumWidth() const {
263 return header_painter_->GetMinimumHeaderWidth();
264 }
265
Layout()266 void CustomFrameViewAsh::HeaderView::Layout() {
267 header_painter_->LayoutHeader(true);
268 header_painter_->set_header_height(GetPreferredHeight());
269 }
270
OnPaint(gfx::Canvas * canvas)271 void CustomFrameViewAsh::HeaderView::OnPaint(gfx::Canvas* canvas) {
272 int theme_image_id = 0;
273 if (frame_->IsMaximized() || frame_->IsFullscreen())
274 theme_image_id = IDR_AURA_WINDOW_HEADER_BASE_MINIMAL;
275 else if (paint_as_active_)
276 theme_image_id = IDR_AURA_WINDOW_HEADER_BASE_ACTIVE;
277 else
278 theme_image_id = IDR_AURA_WINDOW_HEADER_BASE_INACTIVE;
279
280 header_painter_->PaintHeader(
281 canvas,
282 paint_as_active_ ? HeaderPainter::ACTIVE : HeaderPainter::INACTIVE,
283 theme_image_id,
284 0);
285 header_painter_->PaintTitleBar(canvas, GetTitleFont());
286 header_painter_->PaintHeaderContentSeparator(canvas);
287 }
288
OnImmersiveRevealStarted()289 void CustomFrameViewAsh::HeaderView::OnImmersiveRevealStarted() {
290 fullscreen_visible_fraction_ = 0;
291 SetPaintToLayer(true);
292 parent()->Layout();
293 }
294
OnImmersiveRevealEnded()295 void CustomFrameViewAsh::HeaderView::OnImmersiveRevealEnded() {
296 fullscreen_visible_fraction_ = 0;
297 SetPaintToLayer(false);
298 parent()->Layout();
299 }
300
OnImmersiveFullscreenExited()301 void CustomFrameViewAsh::HeaderView::OnImmersiveFullscreenExited() {
302 fullscreen_visible_fraction_ = 0;
303 SetPaintToLayer(false);
304 parent()->Layout();
305 }
306
SetVisibleFraction(double visible_fraction)307 void CustomFrameViewAsh::HeaderView::SetVisibleFraction(
308 double visible_fraction) {
309 if (fullscreen_visible_fraction_ != visible_fraction) {
310 fullscreen_visible_fraction_ = visible_fraction;
311 parent()->Layout();
312 }
313 }
314
315 std::vector<gfx::Rect>
GetVisibleBoundsInScreen() const316 CustomFrameViewAsh::HeaderView::GetVisibleBoundsInScreen() const {
317 // TODO(pkotwicz): Implement views::View::ConvertRectToScreen().
318 gfx::Rect visible_bounds(GetVisibleBounds());
319 gfx::Point visible_origin_in_screen(visible_bounds.origin());
320 views::View::ConvertPointToScreen(this, &visible_origin_in_screen);
321 std::vector<gfx::Rect> bounds_in_screen;
322 bounds_in_screen.push_back(
323 gfx::Rect(visible_origin_in_screen, visible_bounds.size()));
324 if (maximize_bubble_lifetime_observer_.get() &&
325 maximize_bubble_lifetime_observer_->IsWidgetAlive()) {
326 bounds_in_screen.push_back(maximize_bubble_->GetWindowBoundsInScreen());
327 }
328 return bounds_in_screen;
329 }
330
OnMaximizeBubbleShown(views::Widget * bubble)331 void CustomFrameViewAsh::HeaderView::OnMaximizeBubbleShown(
332 views::Widget* bubble) {
333 maximize_bubble_ = bubble;
334 maximize_bubble_lifetime_observer_.reset(
335 new views::WidgetDeletionObserver(bubble));
336 }
337
338 ///////////////////////////////////////////////////////////////////////////////
339 // CustomFrameViewAsh::OverlayView
340
341 // View which takes up the entire widget and contains the HeaderView. HeaderView
342 // is a child of OverlayView to avoid creating a larger texture than necessary
343 // when painting the HeaderView to its own layer.
344 class CustomFrameViewAsh::OverlayView : public views::View {
345 public:
346 explicit OverlayView(HeaderView* header_view);
347 virtual ~OverlayView();
348
349 // views::View override:
350 virtual void Layout() OVERRIDE;
351 virtual bool HitTestRect(const gfx::Rect& rect) const OVERRIDE;
352
353 private:
354 HeaderView* header_view_;
355
356 DISALLOW_COPY_AND_ASSIGN(OverlayView);
357 };
358
OverlayView(HeaderView * header_view)359 CustomFrameViewAsh::OverlayView::OverlayView(HeaderView* header_view)
360 : header_view_(header_view) {
361 AddChildView(header_view);
362 }
363
~OverlayView()364 CustomFrameViewAsh::OverlayView::~OverlayView() {
365 }
366
Layout()367 void CustomFrameViewAsh::OverlayView::Layout() {
368 int onscreen_height = header_view_->GetPreferredOnScreenHeight();
369 if (onscreen_height == 0) {
370 header_view_->SetVisible(false);
371 } else {
372 int height = header_view_->GetPreferredHeight();
373 header_view_->SetBounds(0, onscreen_height - height, width(), height);
374 header_view_->SetVisible(true);
375 }
376 }
377
HitTestRect(const gfx::Rect & rect) const378 bool CustomFrameViewAsh::OverlayView::HitTestRect(const gfx::Rect& rect) const {
379 // Grab events in the header view. Return false for other events so that they
380 // can be handled by the client view.
381 return header_view_->HitTestRect(rect);
382 }
383
384 ////////////////////////////////////////////////////////////////////////////////
385 // CustomFrameViewAsh, public:
386
387 // static
388 const char CustomFrameViewAsh::kViewClassName[] = "CustomFrameViewAsh";
389
CustomFrameViewAsh(views::Widget * frame)390 CustomFrameViewAsh::CustomFrameViewAsh(views::Widget* frame)
391 : frame_(frame),
392 header_view_(new HeaderView(frame)),
393 frame_border_hit_test_controller_(
394 new FrameBorderHitTestController(frame_)) {
395 // |header_view_| is set as the non client view's overlay view so that it can
396 // overlay the web contents in immersive fullscreen.
397 frame->non_client_view()->SetOverlayView(new OverlayView(header_view_));
398
399 // A delegate for a more complex way of fullscreening the window may already
400 // be set. This is the case for packaged apps.
401 wm::WindowState* window_state = wm::GetWindowState(frame->GetNativeWindow());
402 if (!window_state->HasDelegate()) {
403 window_state->SetDelegate(scoped_ptr<wm::WindowStateDelegate>(
404 new CustomFrameViewAshWindowStateDelegate(
405 window_state, this)).Pass());
406 }
407 }
408
~CustomFrameViewAsh()409 CustomFrameViewAsh::~CustomFrameViewAsh() {
410 }
411
InitImmersiveFullscreenControllerForView(ImmersiveFullscreenController * immersive_fullscreen_controller)412 void CustomFrameViewAsh::InitImmersiveFullscreenControllerForView(
413 ImmersiveFullscreenController* immersive_fullscreen_controller) {
414 immersive_fullscreen_controller->Init(header_view_, frame_, header_view_);
415 }
416
417 ////////////////////////////////////////////////////////////////////////////////
418 // CustomFrameViewAsh, views::NonClientFrameView overrides:
419
GetBoundsForClientView() const420 gfx::Rect CustomFrameViewAsh::GetBoundsForClientView() const {
421 int top_height = NonClientTopBorderHeight();
422 return HeaderPainter::GetBoundsForClientView(top_height, bounds());
423 }
424
GetWindowBoundsForClientBounds(const gfx::Rect & client_bounds) const425 gfx::Rect CustomFrameViewAsh::GetWindowBoundsForClientBounds(
426 const gfx::Rect& client_bounds) const {
427 int top_height = NonClientTopBorderHeight();
428 return HeaderPainter::GetWindowBoundsForClientBounds(top_height,
429 client_bounds);
430 }
431
NonClientHitTest(const gfx::Point & point)432 int CustomFrameViewAsh::NonClientHitTest(const gfx::Point& point) {
433 return FrameBorderHitTestController::NonClientHitTest(this,
434 header_view_->header_painter(), point);
435 }
436
GetWindowMask(const gfx::Size & size,gfx::Path * window_mask)437 void CustomFrameViewAsh::GetWindowMask(const gfx::Size& size,
438 gfx::Path* window_mask) {
439 // No window masks in Aura.
440 }
441
ResetWindowControls()442 void CustomFrameViewAsh::ResetWindowControls() {
443 header_view_->ResetWindowControls();
444 }
445
UpdateWindowIcon()446 void CustomFrameViewAsh::UpdateWindowIcon() {
447 }
448
UpdateWindowTitle()449 void CustomFrameViewAsh::UpdateWindowTitle() {
450 header_view_->SchedulePaintForTitle();
451 }
452
453 ////////////////////////////////////////////////////////////////////////////////
454 // CustomFrameViewAsh, views::View overrides:
455
GetPreferredSize()456 gfx::Size CustomFrameViewAsh::GetPreferredSize() {
457 gfx::Size pref = frame_->client_view()->GetPreferredSize();
458 gfx::Rect bounds(0, 0, pref.width(), pref.height());
459 return frame_->non_client_view()->GetWindowBoundsForClientBounds(
460 bounds).size();
461 }
462
GetClassName() const463 const char* CustomFrameViewAsh::GetClassName() const {
464 return kViewClassName;
465 }
466
GetMinimumSize()467 gfx::Size CustomFrameViewAsh::GetMinimumSize() {
468 gfx::Size min_client_view_size(frame_->client_view()->GetMinimumSize());
469 return gfx::Size(
470 std::max(header_view_->GetMinimumWidth(), min_client_view_size.width()),
471 NonClientTopBorderHeight() + min_client_view_size.height());
472 }
473
GetMaximumSize()474 gfx::Size CustomFrameViewAsh::GetMaximumSize() {
475 return frame_->client_view()->GetMaximumSize();
476 }
477
SchedulePaintInRect(const gfx::Rect & r)478 void CustomFrameViewAsh::SchedulePaintInRect(const gfx::Rect& r) {
479 // The HeaderView is not a child of CustomFrameViewAsh. Redirect the paint to
480 // HeaderView instead.
481 header_view_->set_paint_as_active(ShouldPaintAsActive());
482 header_view_->SchedulePaint();
483 }
484
HitTestRect(const gfx::Rect & rect) const485 bool CustomFrameViewAsh::HitTestRect(const gfx::Rect& rect) const {
486 // NonClientView hit tests the NonClientFrameView first instead of going in
487 // z-order. Return false so that events get to the OverlayView.
488 return false;
489 }
490
GetHeaderView()491 views::View* CustomFrameViewAsh::GetHeaderView() {
492 return header_view_;
493 }
494
495 ////////////////////////////////////////////////////////////////////////////////
496 // CustomFrameViewAsh, private:
497
NonClientTopBorderHeight() const498 int CustomFrameViewAsh::NonClientTopBorderHeight() const {
499 return frame_->IsFullscreen() ? 0 : header_view_->GetPreferredHeight();
500 }
501
502 } // namespace ash
503