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 "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 "third_party/skia/include/core/SkRegion.h"
14 #include "ui/base/hit_test.h"
15 #include "ui/base/l10n/l10n_util.h"
16 #include "ui/base/resource/resource_bundle.h"
17 #include "ui/gfx/canvas.h"
18 #include "ui/gfx/image/image.h"
19 #include "ui/gfx/path.h"
20 #include "ui/views/controls/button/image_button.h"
21 #include "ui/views/layout/grid_layout.h"
22 #include "ui/views/views_delegate.h"
23 #include "ui/views/widget/widget.h"
24 #include "ui/views/widget/widget_delegate.h"
27 #include "ui/aura/env.h"
28 #include "ui/aura/window.h"
32 // Height of the chrome-style caption, in pixels.
33 const int kCaptionHeight = 25;
38 const char AppWindowFrameView::kViewClassName[] =
39 "browser/ui/views/extensions/AppWindowFrameView";
41 AppWindowFrameView::AppWindowFrameView()
45 maximize_button_(NULL),
46 restore_button_(NULL),
47 minimize_button_(NULL),
48 resize_inside_bounds_size_(0),
49 resize_outside_bounds_size_(0),
50 resize_area_corner_size_(0) {}
52 AppWindowFrameView::~AppWindowFrameView() {}
54 void AppWindowFrameView::Init(views::Widget* widget,
55 NativeAppWindow* window,
57 const SkColor& frame_color,
58 int resize_inside_bounds_size,
59 int resize_outside_bounds_size,
60 int resize_outside_scale_for_touch,
61 int resize_area_corner_size) {
64 draw_frame_ = draw_frame;
65 frame_color_ = frame_color;
66 resize_inside_bounds_size_ = resize_inside_bounds_size;
67 resize_outside_bounds_size_ = resize_outside_bounds_size;
68 resize_area_corner_size_ = resize_area_corner_size;
71 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
72 close_button_ = new views::ImageButton(this);
73 close_button_->SetImage(
74 views::CustomButton::STATE_NORMAL,
75 rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE).ToImageSkia());
76 close_button_->SetImage(
77 views::CustomButton::STATE_HOVERED,
78 rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_H).ToImageSkia());
79 close_button_->SetImage(
80 views::CustomButton::STATE_PRESSED,
81 rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_P).ToImageSkia());
82 close_button_->SetAccessibleName(
83 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
84 AddChildView(close_button_);
85 maximize_button_ = new views::ImageButton(this);
86 maximize_button_->SetImage(
87 views::CustomButton::STATE_NORMAL,
88 rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE).ToImageSkia());
89 maximize_button_->SetImage(
90 views::CustomButton::STATE_HOVERED,
91 rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_H).ToImageSkia());
92 maximize_button_->SetImage(
93 views::CustomButton::STATE_PRESSED,
94 rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_P).ToImageSkia());
95 maximize_button_->SetImage(
96 views::CustomButton::STATE_DISABLED,
97 rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_D).ToImageSkia());
98 maximize_button_->SetAccessibleName(
99 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE));
100 AddChildView(maximize_button_);
101 restore_button_ = new views::ImageButton(this);
102 restore_button_->SetImage(
103 views::CustomButton::STATE_NORMAL,
104 rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE).ToImageSkia());
105 restore_button_->SetImage(
106 views::CustomButton::STATE_HOVERED,
107 rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE_H).ToImageSkia());
108 restore_button_->SetImage(
109 views::CustomButton::STATE_PRESSED,
110 rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE_P).ToImageSkia());
111 restore_button_->SetAccessibleName(
112 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_RESTORE));
113 AddChildView(restore_button_);
114 minimize_button_ = new views::ImageButton(this);
115 minimize_button_->SetImage(
116 views::CustomButton::STATE_NORMAL,
117 rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE).ToImageSkia());
118 minimize_button_->SetImage(
119 views::CustomButton::STATE_HOVERED,
120 rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE_H).ToImageSkia());
121 minimize_button_->SetImage(
122 views::CustomButton::STATE_PRESSED,
123 rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE_P).ToImageSkia());
124 minimize_button_->SetAccessibleName(
125 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE));
126 AddChildView(minimize_button_);
130 // views::NonClientFrameView implementation.
132 gfx::Rect AppWindowFrameView::GetBoundsForClientView() const {
133 if (!draw_frame_ || widget_->IsFullscreen())
136 0, kCaptionHeight, width(), std::max(0, height() - kCaptionHeight));
139 gfx::Rect AppWindowFrameView::GetWindowBoundsForClientBounds(
140 const gfx::Rect& client_bounds) const {
141 gfx::Rect window_bounds = client_bounds;
142 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
143 // Get the difference between the widget's client area bounds and window
144 // bounds, and grow |window_bounds| by that amount.
145 gfx::Insets native_frame_insets =
146 widget_->GetClientAreaBoundsInScreen().InsetsFrom(
147 widget_->GetWindowBoundsInScreen());
148 window_bounds.Inset(native_frame_insets);
151 // Enforce minimum size (1, 1) in case that client_bounds is passed with
152 // empty size. This could occur when the frameless window is being
154 if (window_bounds.IsEmpty()) {
155 window_bounds.set_width(1);
156 window_bounds.set_height(1);
158 return window_bounds;
161 int closeButtonOffsetX = (kCaptionHeight - close_button_->height()) / 2;
162 int header_width = close_button_->width() + closeButtonOffsetX * 2;
163 return gfx::Rect(window_bounds.x(),
164 window_bounds.y() - kCaptionHeight,
165 std::max(header_width, window_bounds.width()),
166 window_bounds.height() + kCaptionHeight);
169 int AppWindowFrameView::NonClientHitTest(const gfx::Point& point) {
170 if (widget_->IsFullscreen())
173 gfx::Rect expanded_bounds = bounds();
174 if (resize_outside_bounds_size_) {
175 expanded_bounds.Inset(gfx::Insets(-resize_outside_bounds_size_,
176 -resize_outside_bounds_size_,
177 -resize_outside_bounds_size_,
178 -resize_outside_bounds_size_));
180 // Points outside the (possibly expanded) bounds can be discarded.
181 if (!expanded_bounds.Contains(point))
184 // Check the frame first, as we allow a small area overlapping the contents
185 // to be used for resize handles.
186 bool can_ever_resize = widget_->widget_delegate()
187 ? widget_->widget_delegate()->CanResize()
189 // Don't allow overlapping resize handles when the window is maximized or
190 // fullscreen, as it can't be resized in those states.
191 int resize_border = (widget_->IsMaximized() || widget_->IsFullscreen())
193 : resize_inside_bounds_size_;
194 int frame_component = GetHTComponentForFrame(point,
197 resize_area_corner_size_,
198 resize_area_corner_size_,
200 if (frame_component != HTNOWHERE)
201 return frame_component;
203 // Check for possible draggable region in the client area for the frameless
205 SkRegion* draggable_region = window_->GetDraggableRegion();
206 if (draggable_region && draggable_region->contains(point.x(), point.y()))
209 int client_component = widget_->client_view()->NonClientHitTest(point);
210 if (client_component != HTNOWHERE)
211 return client_component;
213 // Then see if the point is within any of the window controls.
214 if (close_button_ && close_button_->visible() &&
215 close_button_->GetMirroredBounds().Contains(point)) {
218 if ((maximize_button_ && maximize_button_->visible() &&
219 maximize_button_->GetMirroredBounds().Contains(point)) ||
220 (restore_button_ && restore_button_->visible() &&
221 restore_button_->GetMirroredBounds().Contains(point))) {
224 if (minimize_button_ && minimize_button_->visible() &&
225 minimize_button_->GetMirroredBounds().Contains(point)) {
229 // Caption is a safe default.
233 void AppWindowFrameView::GetWindowMask(const gfx::Size& size,
234 gfx::Path* window_mask) {
235 // We got nothing to say about no window mask.
238 // views::View implementation.
240 gfx::Size AppWindowFrameView::GetPreferredSize() {
241 gfx::Size pref = widget_->client_view()->GetPreferredSize();
242 gfx::Rect bounds(0, 0, pref.width(), pref.height());
243 return widget_->non_client_view()
244 ->GetWindowBoundsForClientBounds(bounds)
248 void AppWindowFrameView::Layout() {
251 gfx::Size close_size = close_button_->GetPreferredSize();
252 const int kButtonOffsetY = 0;
253 const int kButtonSpacing = 1;
254 const int kRightMargin = 3;
256 close_button_->SetBounds(width() - kRightMargin - close_size.width(),
259 close_size.height());
261 bool can_ever_resize = widget_->widget_delegate()
262 ? widget_->widget_delegate()->CanResize()
264 maximize_button_->SetEnabled(can_ever_resize);
265 gfx::Size maximize_size = maximize_button_->GetPreferredSize();
266 maximize_button_->SetBounds(
267 close_button_->x() - kButtonSpacing - maximize_size.width(),
269 maximize_size.width(),
270 maximize_size.height());
271 gfx::Size restore_size = restore_button_->GetPreferredSize();
272 restore_button_->SetBounds(
273 close_button_->x() - kButtonSpacing - restore_size.width(),
275 restore_size.width(),
276 restore_size.height());
278 bool maximized = widget_->IsMaximized();
279 maximize_button_->SetVisible(!maximized);
280 restore_button_->SetVisible(maximized);
282 maximize_button_->SetState(views::CustomButton::STATE_NORMAL);
284 restore_button_->SetState(views::CustomButton::STATE_NORMAL);
286 gfx::Size minimize_size = minimize_button_->GetPreferredSize();
287 minimize_button_->SetState(views::CustomButton::STATE_NORMAL);
288 minimize_button_->SetBounds(
289 maximize_button_->x() - kButtonSpacing - minimize_size.width(),
291 minimize_size.width(),
292 minimize_size.height());
295 void AppWindowFrameView::OnPaint(gfx::Canvas* canvas) {
299 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
300 if (ShouldPaintAsActive()) {
301 close_button_->SetImage(
302 views::CustomButton::STATE_NORMAL,
303 rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE).ToImageSkia());
305 close_button_->SetImage(
306 views::CustomButton::STATE_NORMAL,
307 rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_U).ToImageSkia());
310 // TODO(jeremya): different look for inactive?
312 paint.setAntiAlias(false);
313 paint.setStyle(SkPaint::kFill_Style);
314 paint.setColor(frame_color_);
317 path.lineTo(width(), 0);
318 path.lineTo(width(), kCaptionHeight);
319 path.lineTo(0, kCaptionHeight);
321 canvas->DrawPath(path, paint);
324 const char* AppWindowFrameView::GetClassName() const { return kViewClassName; }
326 gfx::Size AppWindowFrameView::GetMinimumSize() {
327 gfx::Size min_size = widget_->client_view()->GetMinimumSize();
331 // Ensure we can display the top of the caption area.
332 gfx::Rect client_bounds = GetBoundsForClientView();
333 min_size.Enlarge(0, client_bounds.y());
334 // Ensure we have enough space for the window icon and buttons. We allow
335 // the title string to collapse to zero width.
336 int closeButtonOffsetX = (kCaptionHeight - close_button_->height()) / 2;
337 int header_width = close_button_->width() + closeButtonOffsetX * 2;
338 if (header_width > min_size.width())
339 min_size.set_width(header_width);
343 gfx::Size AppWindowFrameView::GetMaximumSize() {
344 gfx::Size max_size = widget_->client_view()->GetMaximumSize();
346 // Add to the client maximum size the height of any title bar and borders.
347 gfx::Size client_size = GetBoundsForClientView().size();
348 if (max_size.width())
349 max_size.Enlarge(width() - client_size.width(), 0);
350 if (max_size.height())
351 max_size.Enlarge(0, height() - client_size.height());
356 // views::ButtonListener implementation.
358 void AppWindowFrameView::ButtonPressed(views::Button* sender,
359 const ui::Event& event) {
361 if (sender == close_button_)
363 else if (sender == maximize_button_)
365 else if (sender == restore_button_)
367 else if (sender == minimize_button_)