Upstream version 5.34.98.0
[platform/framework/web/crosswalk.git] / src / apps / ui / views / shell_window_frame_view.cc
1 // Copyright 2013 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 "apps/ui/views/shell_window_frame_view.h"
6
7 #include "apps/ui/native_app_window.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "extensions/common/draggable_region.h"
10 #include "grit/theme_resources.h"
11 #include "grit/ui_strings.h"  // Accessibility names
12 #include "third_party/skia/include/core/SkPaint.h"
13 #include "ui/base/hit_test.h"
14 #include "ui/base/l10n/l10n_util.h"
15 #include "ui/base/resource/resource_bundle.h"
16 #include "ui/gfx/canvas.h"
17 #include "ui/gfx/image/image.h"
18 #include "ui/gfx/path.h"
19 #include "ui/views/controls/button/image_button.h"
20 #include "ui/views/layout/grid_layout.h"
21 #include "ui/views/views_delegate.h"
22 #include "ui/views/widget/widget.h"
23 #include "ui/views/widget/widget_delegate.h"
24
25 #if defined(USE_AURA)
26 #include "ui/aura/env.h"
27 #include "ui/aura/window.h"
28 #endif
29
30 namespace {
31 // Height of the chrome-style caption, in pixels.
32 const int kCaptionHeight = 25;
33 }  // namespace
34
35 namespace apps {
36
37 const char ShellWindowFrameView::kViewClassName[] =
38     "browser/ui/views/extensions/ShellWindowFrameView";
39
40 ShellWindowFrameView::ShellWindowFrameView(NativeAppWindow* window)
41     : window_(window),
42       frame_(NULL),
43       close_button_(NULL),
44       maximize_button_(NULL),
45       restore_button_(NULL),
46       minimize_button_(NULL),
47       resize_inside_bounds_size_(0),
48       resize_area_corner_size_(0) {
49 }
50
51 ShellWindowFrameView::~ShellWindowFrameView() {
52 }
53
54 void ShellWindowFrameView::Init(views::Widget* frame,
55                                 int resize_inside_bounds_size,
56                                 int resize_outside_bounds_size,
57                                 int resize_outside_scale_for_touch,
58                                 int resize_area_corner_size) {
59   frame_ = frame;
60   resize_inside_bounds_size_ = resize_inside_bounds_size;
61   resize_area_corner_size_ = resize_area_corner_size;
62
63   if (!window_->IsFrameless()) {
64     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
65     close_button_ = new views::ImageButton(this);
66     close_button_->SetImage(views::CustomButton::STATE_NORMAL,
67         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE).ToImageSkia());
68     close_button_->SetImage(views::CustomButton::STATE_HOVERED,
69         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_H).ToImageSkia());
70     close_button_->SetImage(views::CustomButton::STATE_PRESSED,
71         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_P).ToImageSkia());
72     close_button_->SetAccessibleName(
73         l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
74     AddChildView(close_button_);
75     maximize_button_ = new views::ImageButton(this);
76     maximize_button_->SetImage(views::CustomButton::STATE_NORMAL,
77         rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE).ToImageSkia());
78     maximize_button_->SetImage(views::CustomButton::STATE_HOVERED,
79         rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_H).ToImageSkia());
80     maximize_button_->SetImage(views::CustomButton::STATE_PRESSED,
81         rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_P).ToImageSkia());
82     maximize_button_->SetImage(views::CustomButton::STATE_DISABLED,
83         rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_D).ToImageSkia());
84     maximize_button_->SetAccessibleName(
85         l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE));
86     AddChildView(maximize_button_);
87     restore_button_ = new views::ImageButton(this);
88     restore_button_->SetImage(views::CustomButton::STATE_NORMAL,
89         rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE).ToImageSkia());
90     restore_button_->SetImage(views::CustomButton::STATE_HOVERED,
91         rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE_H).ToImageSkia());
92     restore_button_->SetImage(views::CustomButton::STATE_PRESSED,
93         rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE_P).ToImageSkia());
94     restore_button_->SetAccessibleName(
95         l10n_util::GetStringUTF16(IDS_APP_ACCNAME_RESTORE));
96     AddChildView(restore_button_);
97     minimize_button_ = new views::ImageButton(this);
98     minimize_button_->SetImage(views::CustomButton::STATE_NORMAL,
99         rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE).ToImageSkia());
100     minimize_button_->SetImage(views::CustomButton::STATE_HOVERED,
101         rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE_H).ToImageSkia());
102     minimize_button_->SetImage(views::CustomButton::STATE_PRESSED,
103         rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE_P).ToImageSkia());
104     minimize_button_->SetAccessibleName(
105         l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE));
106     AddChildView(minimize_button_);
107   }
108
109 #if defined(USE_AURA)
110   aura::Window* window = frame->GetNativeWindow();
111   // Some Aura implementations (Ash) allow resize handles outside the window.
112   if (resize_outside_bounds_size > 0) {
113     gfx::Insets mouse_insets = gfx::Insets(-resize_outside_bounds_size,
114                                            -resize_outside_bounds_size,
115                                            -resize_outside_bounds_size,
116                                            -resize_outside_bounds_size);
117     gfx::Insets touch_insets =
118         mouse_insets.Scale(resize_outside_scale_for_touch);
119     // Ensure we get resize cursors for a few pixels outside our bounds.
120     window->SetHitTestBoundsOverrideOuter(mouse_insets, touch_insets);
121   }
122   // Ensure we get resize cursors just inside our bounds as well.
123   // TODO(jeremya): do we need to update these when in fullscreen/maximized?
124   window->set_hit_test_bounds_override_inner(
125       gfx::Insets(resize_inside_bounds_size_, resize_inside_bounds_size_,
126                   resize_inside_bounds_size_, resize_inside_bounds_size_));
127 #endif
128 }
129
130 // views::NonClientFrameView implementation.
131
132 gfx::Rect ShellWindowFrameView::GetBoundsForClientView() const {
133   if (window_->IsFrameless() || frame_->IsFullscreen())
134     return bounds();
135   return gfx::Rect(0, kCaptionHeight, width(),
136       std::max(0, height() - kCaptionHeight));
137 }
138
139 gfx::Rect ShellWindowFrameView::GetWindowBoundsForClientBounds(
140       const gfx::Rect& client_bounds) const {
141   if (window_->IsFrameless()) {
142     gfx::Rect window_bounds = client_bounds;
143     // Enforce minimum size (1, 1) in case that client_bounds is passed with
144     // empty size. This could occur when the frameless window is being
145     // initialized.
146     if (window_bounds.IsEmpty()) {
147       window_bounds.set_width(1);
148       window_bounds.set_height(1);
149     }
150     return window_bounds;
151   }
152
153   int closeButtonOffsetX =
154       (kCaptionHeight - close_button_->height()) / 2;
155   int header_width = close_button_->width() + closeButtonOffsetX * 2;
156   return gfx::Rect(client_bounds.x(),
157                    std::max(0, client_bounds.y() - kCaptionHeight),
158                    std::max(header_width, client_bounds.width()),
159                    client_bounds.height() + kCaptionHeight);
160 }
161
162 int ShellWindowFrameView::NonClientHitTest(const gfx::Point& point) {
163   if (frame_->IsFullscreen())
164     return HTCLIENT;
165
166   gfx::Rect expanded_bounds = bounds();
167 #if defined(USE_AURA)
168   // Some Aura implementations (Ash) optionally allow resize handles just
169   // outside the window bounds.
170   aura::Window* window = frame_->GetNativeWindow();
171   if (aura::Env::GetInstance()->is_touch_down())
172     expanded_bounds.Inset(window->hit_test_bounds_override_outer_touch());
173   else
174     expanded_bounds.Inset(window->hit_test_bounds_override_outer_mouse());
175 #endif
176   // Points outside the (possibly expanded) bounds can be discarded.
177   if (!expanded_bounds.Contains(point))
178     return HTNOWHERE;
179
180   // Check the frame first, as we allow a small area overlapping the contents
181   // to be used for resize handles.
182   bool can_ever_resize = frame_->widget_delegate() ?
183       frame_->widget_delegate()->CanResize() :
184       false;
185   // Don't allow overlapping resize handles when the window is maximized or
186   // fullscreen, as it can't be resized in those states.
187   int resize_border =
188       (frame_->IsMaximized() || frame_->IsFullscreen()) ? 0 :
189       resize_inside_bounds_size_;
190   int frame_component = GetHTComponentForFrame(point,
191                                                resize_border,
192                                                resize_border,
193                                                resize_area_corner_size_,
194                                                resize_area_corner_size_,
195                                                can_ever_resize);
196   if (frame_component != HTNOWHERE)
197     return frame_component;
198
199   // Check for possible draggable region in the client area for the frameless
200   // window.
201   if (window_->IsFrameless()) {
202     SkRegion* draggable_region = window_->GetDraggableRegion();
203     if (draggable_region && draggable_region->contains(point.x(), point.y()))
204       return HTCAPTION;
205   }
206
207   int client_component = frame_->client_view()->NonClientHitTest(point);
208   if (client_component != HTNOWHERE)
209     return client_component;
210
211   // Then see if the point is within any of the window controls.
212   if (close_button_ && close_button_->visible() &&
213       close_button_->GetMirroredBounds().Contains(point)) {
214     return HTCLOSE;
215   }
216   if ((maximize_button_ && maximize_button_->visible() &&
217        maximize_button_->GetMirroredBounds().Contains(point)) ||
218       (restore_button_ && restore_button_->visible() &&
219        restore_button_->GetMirroredBounds().Contains(point))) {
220     return HTMAXBUTTON;
221   }
222   if (minimize_button_ && minimize_button_->visible() &&
223       minimize_button_->GetMirroredBounds().Contains(point)) {
224     return HTMINBUTTON;
225   }
226
227   // Caption is a safe default.
228   return HTCAPTION;
229 }
230
231 void ShellWindowFrameView::GetWindowMask(const gfx::Size& size,
232                                          gfx::Path* window_mask) {
233   // We got nothing to say about no window mask.
234 }
235
236 // views::View implementation.
237
238 gfx::Size ShellWindowFrameView::GetPreferredSize() {
239   gfx::Size pref = frame_->client_view()->GetPreferredSize();
240   gfx::Rect bounds(0, 0, pref.width(), pref.height());
241   return frame_->non_client_view()->GetWindowBoundsForClientBounds(
242       bounds).size();
243 }
244
245 void ShellWindowFrameView::Layout() {
246   if (window_->IsFrameless())
247     return;
248   gfx::Size close_size = close_button_->GetPreferredSize();
249   const int kButtonOffsetY = 0;
250   const int kButtonSpacing = 1;
251   const int kRightMargin = 3;
252
253   close_button_->SetBounds(
254       width() - kRightMargin - close_size.width(),
255       kButtonOffsetY,
256       close_size.width(),
257       close_size.height());
258
259   bool can_ever_resize = frame_->widget_delegate() ?
260       frame_->widget_delegate()->CanResize() :
261       false;
262   maximize_button_->SetEnabled(can_ever_resize);
263   gfx::Size maximize_size = maximize_button_->GetPreferredSize();
264   maximize_button_->SetBounds(
265       close_button_->x() - kButtonSpacing - maximize_size.width(),
266       kButtonOffsetY,
267       maximize_size.width(),
268       maximize_size.height());
269   gfx::Size restore_size = restore_button_->GetPreferredSize();
270   restore_button_->SetBounds(
271       close_button_->x() - kButtonSpacing - restore_size.width(),
272       kButtonOffsetY,
273       restore_size.width(),
274       restore_size.height());
275
276   bool maximized = frame_->IsMaximized();
277   maximize_button_->SetVisible(!maximized);
278   restore_button_->SetVisible(maximized);
279   if (maximized)
280     maximize_button_->SetState(views::CustomButton::STATE_NORMAL);
281   else
282     restore_button_->SetState(views::CustomButton::STATE_NORMAL);
283
284   gfx::Size minimize_size = minimize_button_->GetPreferredSize();
285   minimize_button_->SetBounds(
286       maximize_button_->x() - kButtonSpacing - minimize_size.width(),
287       kButtonOffsetY,
288       minimize_size.width(),
289       minimize_size.height());
290 }
291
292 void ShellWindowFrameView::OnPaint(gfx::Canvas* canvas) {
293   if (window_->IsFrameless())
294     return;
295
296   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
297   if (ShouldPaintAsActive()) {
298     close_button_->SetImage(views::CustomButton::STATE_NORMAL,
299         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE).ToImageSkia());
300   } else {
301     close_button_->SetImage(views::CustomButton::STATE_NORMAL,
302         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_U).ToImageSkia());
303   }
304
305   // TODO(jeremya): different look for inactive?
306   SkPaint paint;
307   paint.setAntiAlias(false);
308   paint.setStyle(SkPaint::kFill_Style);
309   paint.setColor(SK_ColorWHITE);
310   gfx::Path path;
311   path.moveTo(0, 0);
312   path.lineTo(width(), 0);
313   path.lineTo(width(), kCaptionHeight);
314   path.lineTo(0, kCaptionHeight);
315   path.close();
316   canvas->DrawPath(path, paint);
317 }
318
319 const char* ShellWindowFrameView::GetClassName() const {
320   return kViewClassName;
321 }
322
323 gfx::Size ShellWindowFrameView::GetMinimumSize() {
324   gfx::Size min_size = frame_->client_view()->GetMinimumSize();
325   if (window_->IsFrameless())
326     return min_size;
327
328   // Ensure we can display the top of the caption area.
329   gfx::Rect client_bounds = GetBoundsForClientView();
330   min_size.Enlarge(0, client_bounds.y());
331   // Ensure we have enough space for the window icon and buttons.  We allow
332   // the title string to collapse to zero width.
333   int closeButtonOffsetX =
334       (kCaptionHeight - close_button_->height()) / 2;
335   int header_width = close_button_->width() + closeButtonOffsetX * 2;
336   if (header_width > min_size.width())
337     min_size.set_width(header_width);
338   return min_size;
339 }
340
341 gfx::Size ShellWindowFrameView::GetMaximumSize() {
342   gfx::Size max_size = frame_->client_view()->GetMaximumSize();
343
344   // Add to the client maximum size the height of any title bar and borders.
345   gfx::Size client_size = GetBoundsForClientView().size();
346   if (max_size.width())
347     max_size.Enlarge(width() - client_size.width(), 0);
348   if (max_size.height())
349     max_size.Enlarge(0, height() - client_size.height());
350
351   return max_size;
352 }
353
354 // views::ButtonListener implementation.
355
356 void ShellWindowFrameView::ButtonPressed(views::Button* sender,
357                                          const ui::Event& event) {
358   DCHECK(!window_->IsFrameless());
359   if (sender == close_button_)
360     frame_->Close();
361   else if (sender == maximize_button_)
362     frame_->Maximize();
363   else if (sender == restore_button_)
364     frame_->Restore();
365   else if (sender == minimize_button_)
366     frame_->Minimize();
367 }
368
369 }  // namespace apps