Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / apps / ui / views / app_window_frame_view.cc
blob4b6246b2b8ded9cd8cb2820b581f72bbb30328c6
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/widget/widget.h"
24 #include "ui/views/widget/widget_delegate.h"
26 namespace {
28 const int kDefaultResizeInsideBoundsSize = 5;
29 const int kDefaultResizeAreaCornerSize = 16;
30 const int kCaptionHeight = 25;
32 } // namespace
34 namespace apps {
36 const char AppWindowFrameView::kViewClassName[] =
37 "browser/ui/views/extensions/AppWindowFrameView";
39 AppWindowFrameView::AppWindowFrameView(views::Widget* widget,
40 extensions::NativeAppWindow* window,
41 bool draw_frame,
42 const SkColor& active_frame_color,
43 const SkColor& inactive_frame_color)
44 : widget_(widget),
45 window_(window),
46 draw_frame_(draw_frame),
47 active_frame_color_(active_frame_color),
48 inactive_frame_color_(inactive_frame_color),
49 close_button_(NULL),
50 maximize_button_(NULL),
51 restore_button_(NULL),
52 minimize_button_(NULL),
53 resize_inside_bounds_size_(kDefaultResizeInsideBoundsSize),
54 resize_outside_bounds_size_(0),
55 resize_area_corner_size_(kDefaultResizeAreaCornerSize) {
58 AppWindowFrameView::~AppWindowFrameView() {}
60 void AppWindowFrameView::Init() {
61 if (draw_frame_) {
62 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
63 close_button_ = new views::ImageButton(this);
64 close_button_->SetImage(
65 views::CustomButton::STATE_NORMAL,
66 rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE).ToImageSkia());
67 close_button_->SetImage(
68 views::CustomButton::STATE_HOVERED,
69 rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_H).ToImageSkia());
70 close_button_->SetImage(
71 views::CustomButton::STATE_PRESSED,
72 rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_P).ToImageSkia());
73 close_button_->SetAccessibleName(
74 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
75 AddChildView(close_button_);
76 // STATE_NORMAL images are set in SetButtonImagesForFrame, not here.
77 maximize_button_ = new views::ImageButton(this);
78 maximize_button_->SetImage(
79 views::CustomButton::STATE_HOVERED,
80 rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_H).ToImageSkia());
81 maximize_button_->SetImage(
82 views::CustomButton::STATE_PRESSED,
83 rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_P).ToImageSkia());
84 maximize_button_->SetImage(
85 views::CustomButton::STATE_DISABLED,
86 rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_D).ToImageSkia());
87 maximize_button_->SetAccessibleName(
88 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE));
89 AddChildView(maximize_button_);
90 restore_button_ = new views::ImageButton(this);
91 restore_button_->SetImage(
92 views::CustomButton::STATE_HOVERED,
93 rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE_H).ToImageSkia());
94 restore_button_->SetImage(
95 views::CustomButton::STATE_PRESSED,
96 rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE_P).ToImageSkia());
97 restore_button_->SetAccessibleName(
98 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_RESTORE));
99 AddChildView(restore_button_);
100 minimize_button_ = new views::ImageButton(this);
101 minimize_button_->SetImage(
102 views::CustomButton::STATE_HOVERED,
103 rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE_H).ToImageSkia());
104 minimize_button_->SetImage(
105 views::CustomButton::STATE_PRESSED,
106 rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE_P).ToImageSkia());
107 minimize_button_->SetAccessibleName(
108 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE));
109 AddChildView(minimize_button_);
111 SetButtonImagesForFrame();
115 void AppWindowFrameView::SetResizeSizes(int resize_inside_bounds_size,
116 int resize_outside_bounds_size,
117 int resize_area_corner_size) {
118 resize_inside_bounds_size_ = resize_inside_bounds_size;
119 resize_outside_bounds_size_ = resize_outside_bounds_size;
120 resize_area_corner_size_ = resize_area_corner_size;
123 // views::NonClientFrameView implementation.
125 gfx::Rect AppWindowFrameView::GetBoundsForClientView() const {
126 if (!draw_frame_ || widget_->IsFullscreen())
127 return bounds();
128 return gfx::Rect(
129 0, kCaptionHeight, width(), std::max(0, height() - kCaptionHeight));
132 gfx::Rect AppWindowFrameView::GetWindowBoundsForClientBounds(
133 const gfx::Rect& client_bounds) const {
134 gfx::Rect window_bounds = client_bounds;
135 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
136 // Get the difference between the widget's client area bounds and window
137 // bounds, and grow |window_bounds| by that amount.
138 gfx::Insets native_frame_insets =
139 widget_->GetClientAreaBoundsInScreen().InsetsFrom(
140 widget_->GetWindowBoundsInScreen());
141 window_bounds.Inset(native_frame_insets);
142 #endif
143 if (!draw_frame_) {
144 // Enforce minimum size (1, 1) in case that client_bounds is passed with
145 // empty size. This could occur when the frameless window is being
146 // initialized.
147 if (window_bounds.IsEmpty()) {
148 window_bounds.set_width(1);
149 window_bounds.set_height(1);
151 return window_bounds;
154 int closeButtonOffsetX = (kCaptionHeight - close_button_->height()) / 2;
155 int header_width = close_button_->width() + closeButtonOffsetX * 2;
156 return gfx::Rect(window_bounds.x(),
157 window_bounds.y() - kCaptionHeight,
158 std::max(header_width, window_bounds.width()),
159 window_bounds.height() + kCaptionHeight);
162 int AppWindowFrameView::NonClientHitTest(const gfx::Point& point) {
163 if (widget_->IsFullscreen())
164 return HTCLIENT;
166 gfx::Rect expanded_bounds = bounds();
167 if (resize_outside_bounds_size_) {
168 expanded_bounds.Inset(gfx::Insets(-resize_outside_bounds_size_,
169 -resize_outside_bounds_size_,
170 -resize_outside_bounds_size_,
171 -resize_outside_bounds_size_));
173 // Points outside the (possibly expanded) bounds can be discarded.
174 if (!expanded_bounds.Contains(point))
175 return HTNOWHERE;
177 // Check the frame first, as we allow a small area overlapping the contents
178 // to be used for resize handles.
179 bool can_ever_resize = widget_->widget_delegate()
180 ? widget_->widget_delegate()->CanResize()
181 : false;
182 // Don't allow overlapping resize handles when the window is maximized or
183 // fullscreen, as it can't be resized in those states.
184 int resize_border = (widget_->IsMaximized() || widget_->IsFullscreen())
186 : resize_inside_bounds_size_;
187 int frame_component = GetHTComponentForFrame(point,
188 resize_border,
189 resize_border,
190 resize_area_corner_size_,
191 resize_area_corner_size_,
192 can_ever_resize);
193 if (frame_component != HTNOWHERE)
194 return frame_component;
196 // Check for possible draggable region in the client area for the frameless
197 // window.
198 SkRegion* draggable_region = window_->GetDraggableRegion();
199 if (draggable_region && draggable_region->contains(point.x(), point.y()))
200 return HTCAPTION;
202 int client_component = widget_->client_view()->NonClientHitTest(point);
203 if (client_component != HTNOWHERE)
204 return client_component;
206 // Then see if the point is within any of the window controls.
207 if (close_button_ && close_button_->visible() &&
208 close_button_->GetMirroredBounds().Contains(point)) {
209 return HTCLOSE;
211 if ((maximize_button_ && maximize_button_->visible() &&
212 maximize_button_->GetMirroredBounds().Contains(point)) ||
213 (restore_button_ && restore_button_->visible() &&
214 restore_button_->GetMirroredBounds().Contains(point))) {
215 return HTMAXBUTTON;
217 if (minimize_button_ && minimize_button_->visible() &&
218 minimize_button_->GetMirroredBounds().Contains(point)) {
219 return HTMINBUTTON;
222 // Caption is a safe default.
223 return HTCAPTION;
226 void AppWindowFrameView::GetWindowMask(const gfx::Size& size,
227 gfx::Path* window_mask) {
228 // We got nothing to say about no window mask.
231 void AppWindowFrameView::SizeConstraintsChanged() {
232 if (draw_frame_) {
233 maximize_button_->SetEnabled(widget_->widget_delegate() &&
234 widget_->widget_delegate()->CanMaximize());
238 gfx::Size AppWindowFrameView::GetPreferredSize() const {
239 gfx::Size pref = widget_->client_view()->GetPreferredSize();
240 gfx::Rect bounds(0, 0, pref.width(), pref.height());
241 return widget_->non_client_view()
242 ->GetWindowBoundsForClientBounds(bounds)
243 .size();
246 void AppWindowFrameView::Layout() {
247 if (!draw_frame_)
248 return;
249 gfx::Size close_size = close_button_->GetPreferredSize();
250 const int kButtonOffsetY = 0;
251 const int kButtonSpacing = 1;
252 const int kRightMargin = 3;
254 close_button_->SetBounds(width() - kRightMargin - close_size.width(),
255 kButtonOffsetY,
256 close_size.width(),
257 close_size.height());
259 maximize_button_->SetEnabled(widget_->widget_delegate() &&
260 widget_->widget_delegate()->CanMaximize());
261 gfx::Size maximize_size = maximize_button_->GetPreferredSize();
262 maximize_button_->SetBounds(
263 close_button_->x() - kButtonSpacing - maximize_size.width(),
264 kButtonOffsetY,
265 maximize_size.width(),
266 maximize_size.height());
267 gfx::Size restore_size = restore_button_->GetPreferredSize();
268 restore_button_->SetBounds(
269 close_button_->x() - kButtonSpacing - restore_size.width(),
270 kButtonOffsetY,
271 restore_size.width(),
272 restore_size.height());
274 bool maximized = widget_->IsMaximized();
275 maximize_button_->SetVisible(!maximized);
276 restore_button_->SetVisible(maximized);
277 if (maximized)
278 maximize_button_->SetState(views::CustomButton::STATE_NORMAL);
279 else
280 restore_button_->SetState(views::CustomButton::STATE_NORMAL);
282 gfx::Size minimize_size = minimize_button_->GetPreferredSize();
283 minimize_button_->SetState(views::CustomButton::STATE_NORMAL);
284 minimize_button_->SetBounds(
285 maximize_button_->x() - kButtonSpacing - minimize_size.width(),
286 kButtonOffsetY,
287 minimize_size.width(),
288 minimize_size.height());
291 void AppWindowFrameView::OnPaint(gfx::Canvas* canvas) {
292 if (!draw_frame_)
293 return;
295 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
296 if (ShouldPaintAsActive()) {
297 close_button_->SetImage(
298 views::CustomButton::STATE_NORMAL,
299 rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE).ToImageSkia());
300 } else {
301 close_button_->SetImage(
302 views::CustomButton::STATE_NORMAL,
303 rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_U).ToImageSkia());
306 SetButtonImagesForFrame();
307 // TODO(benwells): different look for inactive by default.
308 SkPaint paint;
309 paint.setAntiAlias(false);
310 paint.setStyle(SkPaint::kFill_Style);
311 paint.setColor(CurrentFrameColor());
312 gfx::Path path;
313 path.moveTo(0, 0);
314 path.lineTo(width(), 0);
315 path.lineTo(width(), kCaptionHeight);
316 path.lineTo(0, kCaptionHeight);
317 path.close();
318 canvas->DrawPath(path, paint);
321 const char* AppWindowFrameView::GetClassName() const { return kViewClassName; }
323 gfx::Size AppWindowFrameView::GetMinimumSize() const {
324 gfx::Size min_size = widget_->client_view()->GetMinimumSize();
325 if (!draw_frame_) {
326 min_size.SetToMax(gfx::Size(1, 1));
327 return min_size;
330 // Ensure we can display the top of the caption area.
331 gfx::Rect client_bounds = GetBoundsForClientView();
332 min_size.Enlarge(0, client_bounds.y());
333 // Ensure we have enough space for the window icon and buttons. We allow
334 // the title string to collapse to zero width.
335 int closeButtonOffsetX = (kCaptionHeight - close_button_->height()) / 2;
336 int header_width = close_button_->width() + closeButtonOffsetX * 2;
337 if (header_width > min_size.width())
338 min_size.set_width(header_width);
339 return min_size;
342 gfx::Size AppWindowFrameView::GetMaximumSize() const {
343 gfx::Size max_size = widget_->client_view()->GetMaximumSize();
345 // Add to the client maximum size the height of any title bar and borders.
346 gfx::Size client_size = GetBoundsForClientView().size();
347 if (max_size.width())
348 max_size.Enlarge(width() - client_size.width(), 0);
349 if (max_size.height())
350 max_size.Enlarge(0, height() - client_size.height());
352 return max_size;
355 void AppWindowFrameView::ButtonPressed(views::Button* sender,
356 const ui::Event& event) {
357 DCHECK(draw_frame_);
358 if (sender == close_button_)
359 widget_->Close();
360 else if (sender == maximize_button_)
361 widget_->Maximize();
362 else if (sender == restore_button_)
363 widget_->Restore();
364 else if (sender == minimize_button_)
365 widget_->Minimize();
368 SkColor AppWindowFrameView::CurrentFrameColor() {
369 return widget_->IsActive() ? active_frame_color_ : inactive_frame_color_;
372 void AppWindowFrameView::SetButtonImagesForFrame() {
373 DCHECK(draw_frame_);
375 // If the frame is dark, we should use the light images so they have
376 // some contrast.
377 unsigned char frame_luma =
378 color_utils::GetLuminanceForColor(CurrentFrameColor());
379 const unsigned char kLuminanceThreshold = 100;
380 bool use_light = frame_luma < kLuminanceThreshold;
382 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
383 if (use_light) {
384 maximize_button_->SetImage(
385 views::CustomButton::STATE_NORMAL,
386 rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_L).ToImageSkia());
387 restore_button_->SetImage(
388 views::CustomButton::STATE_NORMAL,
389 rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE_L).ToImageSkia());
390 minimize_button_->SetImage(
391 views::CustomButton::STATE_NORMAL,
392 rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE_L).ToImageSkia());
393 } else {
394 maximize_button_->SetImage(
395 views::CustomButton::STATE_NORMAL,
396 rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE).ToImageSkia());
397 restore_button_->SetImage(
398 views::CustomButton::STATE_NORMAL,
399 rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE).ToImageSkia());
400 minimize_button_->SetImage(
401 views::CustomButton::STATE_NORMAL,
402 rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE).ToImageSkia());
406 } // namespace apps