• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "apps/ui/views/app_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 "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/color_utils.h"
19 #include "ui/gfx/image/image.h"
20 #include "ui/gfx/path.h"
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"
26 
27 namespace {
28 
29 const int kDefaultResizeInsideBoundsSize = 5;
30 const int kDefaultResizeAreaCornerSize = 16;
31 const int kCaptionHeight = 25;
32 
33 }  // namespace
34 
35 namespace apps {
36 
37 const char AppWindowFrameView::kViewClassName[] =
38     "browser/ui/views/extensions/AppWindowFrameView";
39 
AppWindowFrameView(views::Widget * widget,NativeAppWindow * window,bool draw_frame,const SkColor & active_frame_color,const SkColor & inactive_frame_color)40 AppWindowFrameView::AppWindowFrameView(views::Widget* widget,
41                                        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) {
57 }
58 
~AppWindowFrameView()59 AppWindowFrameView::~AppWindowFrameView() {}
60 
Init()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_);
111 
112     SetButtonImagesForFrame();
113   }
114 }
115 
SetResizeSizes(int resize_inside_bounds_size,int resize_outside_bounds_size,int resize_area_corner_size)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;
122 }
123 
124 // views::NonClientFrameView implementation.
125 
GetBoundsForClientView() const126 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));
131 }
132 
GetWindowBoundsForClientBounds(const gfx::Rect & client_bounds) const133 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);
151     }
152     return window_bounds;
153   }
154 
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);
161 }
162 
NonClientHitTest(const gfx::Point & point)163 int AppWindowFrameView::NonClientHitTest(const gfx::Point& point) {
164   if (widget_->IsFullscreen())
165     return HTCLIENT;
166 
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_));
173   }
174   // Points outside the (possibly expanded) bounds can be discarded.
175   if (!expanded_bounds.Contains(point))
176     return HTNOWHERE;
177 
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())
186                           ? 0
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;
196 
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;
202 
203   int client_component = widget_->client_view()->NonClientHitTest(point);
204   if (client_component != HTNOWHERE)
205     return client_component;
206 
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;
211   }
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;
217   }
218   if (minimize_button_ && minimize_button_->visible() &&
219       minimize_button_->GetMirroredBounds().Contains(point)) {
220     return HTMINBUTTON;
221   }
222 
223   // Caption is a safe default.
224   return HTCAPTION;
225 }
226 
GetWindowMask(const gfx::Size & size,gfx::Path * window_mask)227 void AppWindowFrameView::GetWindowMask(const gfx::Size& size,
228                                        gfx::Path* window_mask) {
229   // We got nothing to say about no window mask.
230 }
231 
GetPreferredSize() const232 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();
238 }
239 
Layout()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;
247 
248   close_button_->SetBounds(width() - kRightMargin - close_size.width(),
249                            kButtonOffsetY,
250                            close_size.width(),
251                            close_size.height());
252 
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());
269 
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);
277 
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());
285 }
286 
OnPaint(gfx::Canvas * canvas)287 void AppWindowFrameView::OnPaint(gfx::Canvas* canvas) {
288   if (!draw_frame_)
289     return;
290 
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());
300   }
301 
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);
315 }
316 
GetClassName() const317 const char* AppWindowFrameView::GetClassName() const { return kViewClassName; }
318 
GetMinimumSize() const319 gfx::Size AppWindowFrameView::GetMinimumSize() const {
320   gfx::Size min_size = widget_->client_view()->GetMinimumSize();
321   if (!draw_frame_)
322     return min_size;
323 
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;
334 }
335 
GetMaximumSize() const336 gfx::Size AppWindowFrameView::GetMaximumSize() const {
337   gfx::Size max_size = widget_->client_view()->GetMaximumSize();
338 
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());
345 
346   return max_size;
347 }
348 
ButtonPressed(views::Button * sender,const ui::Event & event)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();
360 }
361 
CurrentFrameColor()362 SkColor AppWindowFrameView::CurrentFrameColor() {
363   return widget_->IsActive() ? active_frame_color_ : inactive_frame_color_;
364 }
365 
SetButtonImagesForFrame()366 void AppWindowFrameView::SetButtonImagesForFrame() {
367   DCHECK(draw_frame_);
368 
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;
375 
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());
397   }
398 }
399 
400 }  // namespace apps
401