OLD | NEW |
| (Empty) |
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 "ash/wm/workspace/frame_caption_button_container_view.h" | |
6 | |
7 #include "ash/ash_switches.h" | |
8 #include "ash/shell.h" | |
9 #include "ash/shell_delegate.h" | |
10 #include "ash/wm/window_settings.h" | |
11 #include "ash/wm/workspace/alternate_frame_caption_button.h" | |
12 #include "ash/wm/workspace/frame_maximize_button.h" | |
13 #include "grit/ash_resources.h" | |
14 #include "grit/ui_strings.h" // Accessibility names | |
15 #include "ui/base/hit_test.h" | |
16 #include "ui/base/l10n/l10n_util.h" | |
17 #include "ui/base/resource/resource_bundle.h" | |
18 #include "ui/compositor/scoped_animation_duration_scale_mode.h" | |
19 #include "ui/gfx/canvas.h" | |
20 #include "ui/views/border.h" | |
21 #include "ui/views/controls/button/image_button.h" | |
22 #include "ui/views/widget/widget.h" | |
23 #include "ui/views/widget/widget_delegate.h" | |
24 #include "ui/views/window/non_client_view.h" | |
25 | |
26 namespace ash { | |
27 | |
28 namespace { | |
29 | |
30 // Constants for normal button style ------------------------------------------- | |
31 | |
32 // The distance between buttons. AlternateFrameCaptionButton::GetXOverlap() is | |
33 // used to compute the distance between buttons for the alternate button style. | |
34 const int kDistanceBetweenButtons = -1; | |
35 | |
36 // Constants for alternate button style ---------------------------------------- | |
37 | |
38 // Spacings between the buttons and the view's top and bottom borders (if any). | |
39 const int kAlternateStyleShortHeaderTopInset = 0; | |
40 const int kAlternateStyleTallHeaderTopInset = 2; | |
41 const int kAlternateStyleShortHeaderBottomInset = 0; | |
42 const int kAlternateStyleTallHeaderBottomInset = 1; | |
43 | |
44 // Ideal spacing between: | |
45 // - Right edge of the leftmost button's overlappable region and the view's left | |
46 // edge. | |
47 // - Left edge of the rightmost button's overlappable region and the view's | |
48 // right edge. | |
49 // Used in GetLeftInset() and GetRightInset(). | |
50 const int kAlternateStyleSideInset = 5; | |
51 | |
52 // Converts |point| from |src| to |dst| and hittests against |dst|. | |
53 bool ConvertPointToViewAndHitTest(const views::View* src, | |
54 const views::View* dst, | |
55 const gfx::Point& point) { | |
56 gfx::Point converted(point); | |
57 views::View::ConvertPointToTarget(src, dst, &converted); | |
58 return dst->HitTestPoint(converted); | |
59 } | |
60 | |
61 } // namespace | |
62 | |
63 // static | |
64 const char FrameCaptionButtonContainerView::kViewClassName[] = | |
65 "FrameCaptionButtonContainerView"; | |
66 | |
67 FrameCaptionButtonContainerView::FrameCaptionButtonContainerView( | |
68 views::NonClientFrameView* frame_view, | |
69 views::Widget* frame, | |
70 MinimizeAllowed minimize_allowed) | |
71 : frame_(frame), | |
72 header_style_(HEADER_STYLE_SHORT), | |
73 minimize_button_(NULL), | |
74 size_button_(NULL), | |
75 close_button_(NULL) { | |
76 bool alternate_style = switches::UseAlternateFrameCaptionButtonStyle(); | |
77 | |
78 // Insert the buttons left to right. | |
79 if (alternate_style) { | |
80 minimize_button_ = new AlternateFrameCaptionButton(this, | |
81 AlternateFrameCaptionButton::ACTION_MINIMIZE); | |
82 size_button_ = new AlternateFrameCaptionButton(this, | |
83 AlternateFrameCaptionButton::ACTION_MAXIMIZE_RESTORE); | |
84 close_button_ = new AlternateFrameCaptionButton(this, | |
85 AlternateFrameCaptionButton::ACTION_CLOSE); | |
86 } else { | |
87 minimize_button_ = new views::ImageButton(this); | |
88 size_button_ = new FrameMaximizeButton(this, frame_view); | |
89 close_button_ = new views::ImageButton(this); | |
90 } | |
91 | |
92 minimize_button_->SetAccessibleName( | |
93 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE)); | |
94 // Hide |minimize_button_| when using the non-alternate button style because | |
95 // |size_button_| is capable of minimizing in this case. | |
96 // TODO(pkotwicz): We should probably show the minimize button when in | |
97 // "always maximized" mode. | |
98 minimize_button_->SetVisible( | |
99 minimize_allowed == MINIMIZE_ALLOWED && | |
100 (alternate_style || !frame_->widget_delegate()->CanMaximize())); | |
101 AddChildView(minimize_button_); | |
102 | |
103 size_button_->SetAccessibleName( | |
104 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE)); | |
105 size_button_->SetVisible(frame_->widget_delegate()->CanMaximize() && | |
106 !ash::Shell::IsForcedMaximizeMode()); | |
107 AddChildView(size_button_); | |
108 | |
109 close_button_->SetAccessibleName( | |
110 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE)); | |
111 AddChildView(close_button_); | |
112 | |
113 button_separator_ = ui::ResourceBundle::GetSharedInstance().GetImageNamed( | |
114 IDR_AURA_WINDOW_BUTTON_SEPARATOR).AsImageSkia(); | |
115 } | |
116 | |
117 FrameCaptionButtonContainerView::~FrameCaptionButtonContainerView() { | |
118 } | |
119 | |
120 void FrameCaptionButtonContainerView::ResetWindowControls() { | |
121 minimize_button_->SetState(views::CustomButton::STATE_NORMAL); | |
122 size_button_->SetState(views::CustomButton::STATE_NORMAL); | |
123 } | |
124 | |
125 int FrameCaptionButtonContainerView::NonClientHitTest( | |
126 const gfx::Point& point) const { | |
127 if (close_button_->visible() && | |
128 ConvertPointToViewAndHitTest(this, close_button_, point)) { | |
129 return HTCLOSE; | |
130 } else if (size_button_->visible() && | |
131 ConvertPointToViewAndHitTest(this, size_button_, point)) { | |
132 return HTMAXBUTTON; | |
133 } else if (minimize_button_->visible() && | |
134 ConvertPointToViewAndHitTest(this, minimize_button_, point)) { | |
135 return HTMINBUTTON; | |
136 } | |
137 return HTNOWHERE; | |
138 } | |
139 | |
140 gfx::Size FrameCaptionButtonContainerView::GetPreferredSize() { | |
141 int button_separation = GetDistanceBetweenButtons(); | |
142 | |
143 int width = 0; | |
144 bool first_visible = true; | |
145 for (int i = 0; i < child_count(); ++i) { | |
146 views::View* child = child_at(i); | |
147 if (!child->visible()) | |
148 continue; | |
149 | |
150 width += child_at(i)->GetPreferredSize().width(); | |
151 if (!first_visible) | |
152 width += button_separation; | |
153 first_visible = false; | |
154 } | |
155 gfx::Insets insets(GetInsets()); | |
156 return gfx::Size( | |
157 width + insets.width() + GetLeftInset() + GetRightInset(), | |
158 close_button_->GetPreferredSize().height() + insets.height()); | |
159 } | |
160 | |
161 void FrameCaptionButtonContainerView::Layout() { | |
162 if (switches::UseAlternateFrameCaptionButtonStyle()) { | |
163 int top_inset = kAlternateStyleShortHeaderTopInset; | |
164 int bottom_inset = kAlternateStyleShortHeaderBottomInset; | |
165 if (header_style_ == HEADER_STYLE_TALL) { | |
166 top_inset = kAlternateStyleTallHeaderTopInset; | |
167 bottom_inset = kAlternateStyleTallHeaderBottomInset; | |
168 } | |
169 | |
170 minimize_button_->set_border( | |
171 views::Border::CreateEmptyBorder(top_inset, 0, bottom_inset, 0)); | |
172 size_button_->set_border( | |
173 views::Border::CreateEmptyBorder(top_inset, 0, bottom_inset, 0)); | |
174 close_button_->set_border( | |
175 views::Border::CreateEmptyBorder(top_inset, 0, bottom_inset, 0)); | |
176 } else { | |
177 SetButtonImages(minimize_button_, | |
178 IDR_AURA_WINDOW_MINIMIZE_SHORT, | |
179 IDR_AURA_WINDOW_MINIMIZE_SHORT_H, | |
180 IDR_AURA_WINDOW_MINIMIZE_SHORT_P); | |
181 | |
182 if (header_style_ == HEADER_STYLE_MAXIMIZED_HOSTED_APP) { | |
183 SetButtonImages(size_button_, | |
184 IDR_AURA_WINDOW_FULLSCREEN_RESTORE, | |
185 IDR_AURA_WINDOW_FULLSCREEN_RESTORE_H, | |
186 IDR_AURA_WINDOW_FULLSCREEN_RESTORE_P); | |
187 SetButtonImages(close_button_, | |
188 IDR_AURA_WINDOW_FULLSCREEN_CLOSE, | |
189 IDR_AURA_WINDOW_FULLSCREEN_CLOSE_H, | |
190 IDR_AURA_WINDOW_FULLSCREEN_CLOSE_P); | |
191 } else if (header_style_ == HEADER_STYLE_SHORT) { | |
192 // The new assets only make sense if the window is maximized or fullscreen | |
193 // because we usually use a black header in this case. | |
194 if ((frame_->IsMaximized() || frame_->IsFullscreen()) && | |
195 wm::GetWindowSettings( | |
196 frame_->GetNativeWindow())->tracked_by_workspace()) { | |
197 SetButtonImages(size_button_, | |
198 IDR_AURA_WINDOW_MAXIMIZED_RESTORE2, | |
199 IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_H, | |
200 IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_P); | |
201 SetButtonImages(close_button_, | |
202 IDR_AURA_WINDOW_MAXIMIZED_CLOSE2, | |
203 IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_H, | |
204 IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_P); | |
205 } else { | |
206 SetButtonImages(size_button_, | |
207 IDR_AURA_WINDOW_MAXIMIZED_RESTORE, | |
208 IDR_AURA_WINDOW_MAXIMIZED_RESTORE_H, | |
209 IDR_AURA_WINDOW_MAXIMIZED_RESTORE_P); | |
210 SetButtonImages(close_button_, | |
211 IDR_AURA_WINDOW_MAXIMIZED_CLOSE, | |
212 IDR_AURA_WINDOW_MAXIMIZED_CLOSE_H, | |
213 IDR_AURA_WINDOW_MAXIMIZED_CLOSE_P); | |
214 } | |
215 } else { | |
216 SetButtonImages(size_button_, | |
217 IDR_AURA_WINDOW_MAXIMIZE, | |
218 IDR_AURA_WINDOW_MAXIMIZE_H, | |
219 IDR_AURA_WINDOW_MAXIMIZE_P); | |
220 SetButtonImages(close_button_, | |
221 IDR_AURA_WINDOW_CLOSE, | |
222 IDR_AURA_WINDOW_CLOSE_H, | |
223 IDR_AURA_WINDOW_CLOSE_P); | |
224 } | |
225 } | |
226 | |
227 gfx::Insets insets(GetInsets()); | |
228 int x = insets.left() + GetLeftInset(); | |
229 int y_inset = insets.top(); | |
230 int button_separation = GetDistanceBetweenButtons(); | |
231 for (int i = 0; i < child_count(); ++i) { | |
232 views::View* child = child_at(i); | |
233 if (!child->visible()) | |
234 continue; | |
235 | |
236 gfx::Size size = child->GetPreferredSize(); | |
237 child->SetBounds(x, y_inset, size.width(), size.height()); | |
238 | |
239 // Do not allow |child| to paint over the left border. | |
240 child->set_clip_insets( | |
241 gfx::Insets(0, std::max(0, insets.left() - x), 0, 0)); | |
242 | |
243 x += size.width() + button_separation; | |
244 } | |
245 } | |
246 | |
247 const char* FrameCaptionButtonContainerView::GetClassName() const { | |
248 return kViewClassName; | |
249 } | |
250 | |
251 void FrameCaptionButtonContainerView::OnPaint(gfx::Canvas* canvas) { | |
252 views::View::OnPaint(canvas); | |
253 | |
254 // The alternate button style and AppNonClientFrameViewAsh do not paint the | |
255 // button separator. | |
256 if (header_style_ != HEADER_STYLE_MAXIMIZED_HOSTED_APP && | |
257 !switches::UseAlternateFrameCaptionButtonStyle()) { | |
258 // We should have at most two visible buttons. The button separator is | |
259 // always painted underneath the close button regardless of whether a | |
260 // button other than the close button is visible. | |
261 gfx::Rect divider(close_button_->bounds().origin(), | |
262 button_separator_.size()); | |
263 canvas->DrawImageInt(button_separator_, | |
264 GetMirroredXForRect(divider), | |
265 divider.y()); | |
266 } | |
267 } | |
268 | |
269 int FrameCaptionButtonContainerView::GetDistanceBetweenButtons() const { | |
270 if (switches::UseAlternateFrameCaptionButtonStyle()) | |
271 return AlternateFrameCaptionButton::GetXOverlap() * -2; | |
272 return kDistanceBetweenButtons; | |
273 } | |
274 | |
275 int FrameCaptionButtonContainerView::GetLeftInset() const { | |
276 if (switches::UseAlternateFrameCaptionButtonStyle()) { | |
277 // If using the alternate button style and there is a border, clip the | |
278 // left overlappable region of the leftmost button to | |
279 // |kAlternateStyleSideInset|. | |
280 // Otherwise, allow enough room for the entire left overlappable region of | |
281 // the leftmost button to fit in the view. | |
282 if (border() && border()->GetInsets().left()) { | |
283 return kAlternateStyleSideInset - | |
284 AlternateFrameCaptionButton::GetXOverlap(); | |
285 } | |
286 } | |
287 return 0; | |
288 } | |
289 | |
290 int FrameCaptionButtonContainerView::GetRightInset() const { | |
291 if (switches::UseAlternateFrameCaptionButtonStyle()) { | |
292 // Always clip the right overlappable region of the rightmost button to | |
293 // |kAlternateStyleSideInset| because the caption buttons are always | |
294 // at the right edge of the screen. (The left edge in RTL mode). | |
295 return kAlternateStyleSideInset - | |
296 AlternateFrameCaptionButton::GetXOverlap(); | |
297 } | |
298 return 0; | |
299 } | |
300 | |
301 void FrameCaptionButtonContainerView::ButtonPressed(views::Button* sender, | |
302 const ui::Event& event) { | |
303 // When shift-clicking, slow down animations for visual debugging. | |
304 // We used to do this via an event filter that looked for the shift key being | |
305 // pressed but this interfered with several normal keyboard shortcuts. | |
306 scoped_ptr<ui::ScopedAnimationDurationScaleMode> slow_duration_mode; | |
307 if (event.IsShiftDown()) { | |
308 slow_duration_mode.reset(new ui::ScopedAnimationDurationScaleMode( | |
309 ui::ScopedAnimationDurationScaleMode::SLOW_DURATION)); | |
310 } | |
311 | |
312 ash::UserMetricsAction action = | |
313 ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MINIMIZE; | |
314 if (sender == minimize_button_) { | |
315 // The minimize button may move out from under the cursor. | |
316 ResetWindowControls(); | |
317 frame_->Minimize(); | |
318 } else if (sender == size_button_) { | |
319 // The size button may move out from under the cursor. | |
320 ResetWindowControls(); | |
321 if (frame_->IsFullscreen()) { // Can be clicked in immersive fullscreen. | |
322 frame_->SetFullscreen(false); | |
323 action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_EXIT_FULLSCREEN; | |
324 } else if (frame_->IsMaximized()) { | |
325 frame_->Restore(); | |
326 action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_RESTORE; | |
327 } else { | |
328 frame_->Maximize(); | |
329 action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MAXIMIZE; | |
330 } | |
331 } else if(sender == close_button_) { | |
332 frame_->Close(); | |
333 action = ash::UMA_WINDOW_CLOSE_BUTTON_CLICK; | |
334 } else { | |
335 return; | |
336 } | |
337 ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction(action); | |
338 } | |
339 | |
340 void FrameCaptionButtonContainerView::SetButtonImages( | |
341 views::CustomButton* button, | |
342 int normal_image_id, | |
343 int hot_image_id, | |
344 int pushed_image_id) { | |
345 // When using the alternate button style, |button| does not inherit from | |
346 // views::ImageButton. | |
347 DCHECK(!switches::UseAlternateFrameCaptionButtonStyle()); | |
348 views::ImageButton* image_button = static_cast<views::ImageButton*>(button); | |
349 ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance(); | |
350 image_button->SetImage(views::CustomButton::STATE_NORMAL, | |
351 resource_bundle.GetImageSkiaNamed(normal_image_id)); | |
352 image_button->SetImage(views::CustomButton::STATE_HOVERED, | |
353 resource_bundle.GetImageSkiaNamed(hot_image_id)); | |
354 image_button->SetImage(views::CustomButton::STATE_PRESSED, | |
355 resource_bundle.GetImageSkiaNamed(pushed_image_id)); | |
356 } | |
357 | |
358 } // namespace ash | |
OLD | NEW |