Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / apps / ui / views / app_window_frame_view.cc
blobfe5722526ba4b19363742af652489629665be23d
1 // Copyright 2014 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 "apps/ui/views/app_window_frame_view.h"
7 #include "base/strings/utf_string_conversions.h"
8 #include "extensions/browser/app_window/native_app_window.h"
9 #include "extensions/common/draggable_region.h"
10 #include "grit/theme_resources.h"
11 #include "third_party/skia/include/core/SkPaint.h"
12 #include "third_party/skia/include/core/SkRegion.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/color_utils.h"
18 #include "ui/gfx/image/image.h"
19 #include "ui/gfx/path.h"
20 #include "ui/strings/grit/ui_strings.h" // Accessibility names
21 #include "ui/views/controls/button/image_button.h"
22 #include "ui/views/layout/grid_layout.h"
23 #include "ui/views/views_delegate.h"
24 #include "ui/views/widget/widget.h"
25 #include "ui/views/widget/widget_delegate.h"
27 namespace {
29 const int kDefaultResizeInsideBoundsSize = 5;
30 const int kDefaultResizeAreaCornerSize = 16;
31 const int kCaptionHeight = 25;
33 } // namespace
35 namespace apps {
37 const char AppWindowFrameView::kViewClassName[] =
38 "browser/ui/views/extensions/AppWindowFrameView";
40 AppWindowFrameView::AppWindowFrameView(views::Widget* widget,
41 extensions::NativeAppWindow* window,
42 bool draw_frame,
43 const SkColor& active_frame_color,
44 const SkColor& inactive_frame_color)
45 : widget_(widget),
46 window_(window),
47 draw_frame_(draw_frame),
48 active_frame_color_(active_frame_color),
49 inactive_frame_color_(inactive_frame_color),
50 close_button_(NULL),
51 maximize_button_(NULL),
52 restore_button_(NULL),
53 minimize_button_(NULL),
54 resize_inside_bounds_size_(kDefaultResizeInsideBoundsSize),
55 resize_outside_bounds_size_(0),
56 resize_area_corner_size_(kDefaultResizeAreaCornerSize) {
59 AppWindowFrameView::~AppWindowFrameView() {}
61 void AppWindowFrameView::Init() {
62 if (draw_frame_) {
63 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
64 close_button_ = new views::ImageButton(this);
65 close_button_->SetImage(
66 views::CustomButton::STATE_NORMAL,
67 rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE).ToImageSkia());
68 close_button_->SetImage(
69 views::CustomButton::STATE_HOVERED,
70 rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_H).ToImageSkia());
71 close_button_->SetImage(
72 views::CustomButton::STATE_PRESSED,
73 rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_P).ToImageSkia());
74 close_button_->SetAccessibleName(
75 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
76 AddChildView(close_button_);
77 // STATE_NORMAL images are set in SetButtonImagesForFrame, not here.
78 maximize_button_ = new views::ImageButton(this);
79 maximize_button_->SetImage(
80 views::CustomButton::STATE_HOVERED,
81 rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_H).ToImageSkia());
82 maximize_button_->SetImage(
83 views::CustomButton::STATE_PRESSED,
84 rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_P).ToImageSkia());
85 maximize_button_->SetImage(
86 views::CustomButton::STATE_DISABLED,
87 rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_D).ToImageSkia());
88 maximize_button_->SetAccessibleName(
89 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE));
90 AddChildView(maximize_button_);
91 restore_button_ = new views::ImageButton(this);
92 restore_button_->SetImage(
93 views::CustomButton::STATE_HOVERED,
94 rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE_H).ToImageSkia());
95 restore_button_->SetImage(
96 views::CustomButton::STATE_PRESSED,
97 rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE_P).ToImageSkia());
98 restore_button_->SetAccessibleName(
99 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_RESTORE));
100 AddChildView(restore_button_);
101 minimize_button_ = new views::ImageButton(this);
102 minimize_button_->SetImage(
103 views::CustomButton::STATE_HOVERED,
104 rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE_H).ToImageSkia());
105 minimize_button_->SetImage(
106 views::CustomButton::STATE_PRESSED,
107 rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE_P).ToImageSkia());
108 minimize_button_->SetAccessibleName(
109 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE));
110 AddChildView(minimize_button_);
112 SetButtonImagesForFrame();
116 void AppWindowFrameView::SetResizeSizes(int resize_inside_bounds_size,
117 int resize_outside_bounds_size,
118 int resize_area_corner_size) {
119 resize_inside_bounds_size_ = resize_inside_bounds_size;
120 resize_outside_bounds_size_ = resize_outside_bounds_size;
121 resize_area_corner_size_ = resize_area_corner_size;
124 // views::NonClientFrameView implementation.
126 gfx::Rect AppWindowFrameView::GetBoundsForClientView() const {
127 if (!draw_frame_ || widget_->IsFullscreen())
128 return bounds();
129 return gfx::Rect(
130 0, kCaptionHeight, width(), std::max(0, height() - kCaptionHeight));
133 gfx::Rect AppWindowFrameView::GetWindowBoundsForClientBounds(
134 const gfx::Rect& client_bounds) const {
135 gfx::Rect window_bounds = client_bounds;
136 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
137 // Get the difference between the widget's client area bounds and window
138 // bounds, and grow |window_bounds| by that amount.
139 gfx::Insets native_frame_insets =
140 widget_->GetClientAreaBoundsInScreen().InsetsFrom(
141 widget_->GetWindowBoundsInScreen());
142 window_bounds.Inset(native_frame_insets);
143 #endif
144 if (!draw_frame_) {
145 // Enforce minimum size (1, 1) in case that client_bounds is passed with
146 // empty size. This could occur when the frameless window is being
147 // initialized.
148 if (window_bounds.IsEmpty()) {
149 window_bounds.set_width(1);
150 window_bounds.set_height(1);
152 return window_bounds;
155 int closeButtonOffsetX = (kCaptionHeight - close_button_->height()) / 2;
156 int header_width = close_button_->width() + closeButtonOffsetX * 2;
157 return gfx::Rect(window_bounds.x(),
158 window_bounds.y() - kCaptionHeight,
159 std::max(header_width, window_bounds.width()),
160 window_bounds.height() + kCaptionHeight);
163 int AppWindowFrameView::NonClientHitTest(const gfx::Point& point) {
164 if (widget_->IsFullscreen())
165 return HTCLIENT;
167 gfx::Rect expanded_bounds = bounds();
168 if (resize_outside_bounds_size_) {
169 expanded_bounds.Inset(gfx::Insets(-resize_outside_bounds_size_,
170 -resize_outside_bounds_size_,
171 -resize_outside_bounds_size_,
172 -resize_outside_bounds_size_));
174 // Points outside the (possibly expanded) bounds can be discarded.
175 if (!expanded_bounds.Contains(point))
176 return HTNOWHERE;
178 // Check the frame first, as we allow a small area overlapping the contents
179 // to be used for resize handles.
180 bool can_ever_resize = widget_->widget_delegate()
181 ? widget_->widget_delegate()->CanResize()
182 : false;
183 // Don't allow overlapping resize handles when the window is maximized or
184 // fullscreen, as it can't be resized in those states.
185 int resize_border = (widget_->IsMaximized() || widget_->IsFullscreen())
187 : resize_inside_bounds_size_;
188 int frame_component = GetHTComponentForFrame(point,
189 resize_border,
190 resize_border,
191 resize_area_corner_size_,
192 resize_area_corner_size_,
193 can_ever_resize);
194 if (frame_component != HTNOWHERE)
195 return frame_component;
197 // Check for possible draggable region in the client area for the frameless
198 // window.
199 SkRegion* draggable_region = window_->GetDraggableRegion();
200 if (draggable_region && draggable_region->contains(point.x(), point.y()))
201 return HTCAPTION;
203 int client_component = widget_->client_view()->NonClientHitTest(point);
204 if (client_component != HTNOWHERE)
205 return client_component;
207 // Then see if the point is within any of the window controls.
208 if (close_button_ && close_button_->visible() &&
209 close_button_->GetMirroredBounds().Contains(point)) {
210 return HTCLOSE;
212 if ((maximize_button_ && maximize_button_->visible() &&
213 maximize_button_->GetMirroredBounds().Contains(point)) ||
214 (restore_button_ && restore_button_->visible() &&
215 restore_button_->GetMirroredBounds().Contains(point))) {
216 return HTMAXBUTTON;
218 if (minimize_button_ && minimize_button_->visible() &&
219 minimize_button_->GetMirroredBounds().Contains(point)) {
220 return HTMINBUTTON;
223 // Caption is a safe default.
224 return HTCAPTION;
227 void AppWindowFrameView::GetWindowMask(const gfx::Size& size,
228 gfx::Path* window_mask) {
229 // We got nothing to say about no window mask.
232 gfx::Size AppWindowFrameView::GetPreferredSize() const {
233 gfx::Size pref = widget_->client_view()->GetPreferredSize();
234 gfx::Rect bounds(0, 0, pref.width(), pref.height());
235 return widget_->non_client_view()
236 ->GetWindowBoundsForClientBounds(bounds)
237 .size();
240 void AppWindowFrameView::Layout() {
241 if (!draw_frame_)
242 return;
243 gfx::Size close_size = close_button_->GetPreferredSize();
244 const int kButtonOffsetY = 0;
245 const int kButtonSpacing = 1;
246 const int kRightMargin = 3;
248 close_button_->SetBounds(width() - kRightMargin - close_size.width(),
249 kButtonOffsetY,
250 close_size.width(),
251 close_size.height());
253 bool can_ever_resize = widget_->widget_delegate()
254 ? widget_->widget_delegate()->CanResize()
255 : false;
256 maximize_button_->SetEnabled(can_ever_resize);
257 gfx::Size maximize_size = maximize_button_->GetPreferredSize();
258 maximize_button_->SetBounds(
259 close_button_->x() - kButtonSpacing - maximize_size.width(),
260 kButtonOffsetY,
261 maximize_size.width(),
262 maximize_size.height());
263 gfx::Size restore_size = restore_button_->GetPreferredSize();
264 restore_button_->SetBounds(
265 close_button_->x() - kButtonSpacing - restore_size.width(),
266 kButtonOffsetY,
267 restore_size.width(),
268 restore_size.height());
270 bool maximized = widget_->IsMaximized();
271 maximize_button_->SetVisible(!maximized);
272 restore_button_->SetVisible(maximized);
273 if (maximized)
274 maximize_button_->SetState(views::CustomButton::STATE_NORMAL);
275 else
276 restore_button_->SetState(views::CustomButton::STATE_NORMAL);
278 gfx::Size minimize_size = minimize_button_->GetPreferredSize();
279 minimize_button_->SetState(views::CustomButton::STATE_NORMAL);
280 minimize_button_->SetBounds(
281 maximize_button_->x() - kButtonSpacing - minimize_size.width(),
282 kButtonOffsetY,
283 minimize_size.width(),
284 minimize_size.height());
287 void AppWindowFrameView::OnPaint(gfx::Canvas* canvas) {
288 if (!draw_frame_)
289 return;
291 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
292 if (ShouldPaintAsActive()) {
293 close_button_->SetImage(
294 views::CustomButton::STATE_NORMAL,
295 rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE).ToImageSkia());
296 } else {
297 close_button_->SetImage(
298 views::CustomButton::STATE_NORMAL,
299 rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_U).ToImageSkia());
302 SetButtonImagesForFrame();
303 // TODO(benwells): different look for inactive by default.
304 SkPaint paint;
305 paint.setAntiAlias(false);
306 paint.setStyle(SkPaint::kFill_Style);
307 paint.setColor(CurrentFrameColor());
308 gfx::Path path;
309 path.moveTo(0, 0);
310 path.lineTo(width(), 0);
311 path.lineTo(width(), kCaptionHeight);
312 path.lineTo(0, kCaptionHeight);
313 path.close();
314 canvas->DrawPath(path, paint);
317 const char* AppWindowFrameView::GetClassName() const { return kViewClassName; }
319 gfx::Size AppWindowFrameView::GetMinimumSize() const {
320 gfx::Size min_size = widget_->client_view()->GetMinimumSize();
321 if (!draw_frame_)
322 return min_size;
324 // Ensure we can display the top of the caption area.
325 gfx::Rect client_bounds = GetBoundsForClientView();
326 min_size.Enlarge(0, client_bounds.y());
327 // Ensure we have enough space for the window icon and buttons. We allow
328 // the title string to collapse to zero width.
329 int closeButtonOffsetX = (kCaptionHeight - close_button_->height()) / 2;
330 int header_width = close_button_->width() + closeButtonOffsetX * 2;
331 if (header_width > min_size.width())
332 min_size.set_width(header_width);
333 return min_size;
336 gfx::Size AppWindowFrameView::GetMaximumSize() const {
337 gfx::Size max_size = widget_->client_view()->GetMaximumSize();
339 // Add to the client maximum size the height of any title bar and borders.
340 gfx::Size client_size = GetBoundsForClientView().size();
341 if (max_size.width())
342 max_size.Enlarge(width() - client_size.width(), 0);
343 if (max_size.height())
344 max_size.Enlarge(0, height() - client_size.height());
346 return max_size;
349 void AppWindowFrameView::ButtonPressed(views::Button* sender,
350 const ui::Event& event) {
351 DCHECK(draw_frame_);
352 if (sender == close_button_)
353 widget_->Close();
354 else if (sender == maximize_button_)
355 widget_->Maximize();
356 else if (sender == restore_button_)
357 widget_->Restore();
358 else if (sender == minimize_button_)
359 widget_->Minimize();
362 SkColor AppWindowFrameView::CurrentFrameColor() {
363 return widget_->IsActive() ? active_frame_color_ : inactive_frame_color_;
366 void AppWindowFrameView::SetButtonImagesForFrame() {
367 DCHECK(draw_frame_);
369 // If the frame is dark, we should use the light images so they have
370 // some contrast.
371 unsigned char frame_luma =
372 color_utils::GetLuminanceForColor(CurrentFrameColor());
373 const unsigned char kLuminanceThreshold = 100;
374 bool use_light = frame_luma < kLuminanceThreshold;
376 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
377 if (use_light) {
378 maximize_button_->SetImage(
379 views::CustomButton::STATE_NORMAL,
380 rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_L).ToImageSkia());
381 restore_button_->SetImage(
382 views::CustomButton::STATE_NORMAL,
383 rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE_L).ToImageSkia());
384 minimize_button_->SetImage(
385 views::CustomButton::STATE_NORMAL,
386 rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE_L).ToImageSkia());
387 } else {
388 maximize_button_->SetImage(
389 views::CustomButton::STATE_NORMAL,
390 rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE).ToImageSkia());
391 restore_button_->SetImage(
392 views::CustomButton::STATE_NORMAL,
393 rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE).ToImageSkia());
394 minimize_button_->SetImage(
395 views::CustomButton::STATE_NORMAL,
396 rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE).ToImageSkia());
400 } // namespace apps