1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
|
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/cocoa/panels/panel_stack_window_cocoa.h"
#include "base/logging.h"
#include "base/strings/sys_string_conversions.h"
#import "chrome/browser/ui/cocoa/panels/panel_cocoa.h"
#import "chrome/browser/ui/cocoa/panels/panel_utils_cocoa.h"
#include "chrome/browser/ui/panels/panel.h"
#include "chrome/browser/ui/panels/panel_manager.h"
#include "chrome/browser/ui/panels/stacked_panel_collection.h"
#include "ui/base/cocoa/window_size_constants.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "ui/snapshot/snapshot.h"
// The delegate class to receive the notification from NSViewAnimation.
@interface BatchBoundsAnimationDelegate : NSObject<NSAnimationDelegate> {
@private
PanelStackWindowCocoa* window_; // Weak pointer.
}
// Called when NSViewAnimation finishes the animation.
- (void)animationDidEnd:(NSAnimation*)animation;
@end
@implementation BatchBoundsAnimationDelegate
- (id)initWithWindow:(PanelStackWindowCocoa*)window {
if ((self = [super init]))
window_ = window;
return self;
}
- (void)animationDidEnd:(NSAnimation*)animation {
window_->BoundsUpdateAnimationEnded();
}
@end
// static
NativePanelStackWindow* NativePanelStackWindow::Create(
NativePanelStackWindowDelegate* delegate) {
return new PanelStackWindowCocoa(delegate);
}
PanelStackWindowCocoa::PanelStackWindowCocoa(
NativePanelStackWindowDelegate* delegate)
: delegate_(delegate),
attention_request_id_(0),
bounds_updates_started_(false),
animate_bounds_updates_(false),
bounds_animation_(nil) {
DCHECK(delegate);
bounds_animation_delegate_.reset(
[[BatchBoundsAnimationDelegate alloc] initWithWindow:this]);
}
PanelStackWindowCocoa::~PanelStackWindowCocoa() {
}
void PanelStackWindowCocoa::Close() {
TerminateBoundsAnimation();
[window_ close];
}
void PanelStackWindowCocoa::AddPanel(Panel* panel) {
panels_.push_back(panel);
EnsureWindowCreated();
// Make the stack window own the panel window such that all panels window
// could be moved simulatenously when the stack window is moved.
[window_ addChildWindow:panel->GetNativeWindow() ordered:NSWindowAbove];
UpdateStackWindowBounds();
}
void PanelStackWindowCocoa::RemovePanel(Panel* panel) {
if (IsAnimatingPanelBounds()) {
// This panel is gone. We should not perform any update to it.
bounds_updates_.erase(panel);
}
panels_.remove(panel);
// If the native panel is closed, the native window should already be gone.
if (!static_cast<PanelCocoa*>(panel->native_panel())->IsClosed())
[window_ removeChildWindow:panel->GetNativeWindow()];
UpdateStackWindowBounds();
}
void PanelStackWindowCocoa::MergeWith(NativePanelStackWindow* another) {
PanelStackWindowCocoa* another_stack =
static_cast<PanelStackWindowCocoa*>(another);
for (Panels::const_iterator iter = another_stack->panels_.begin();
iter != another_stack->panels_.end(); ++iter) {
Panel* panel = *iter;
panels_.push_back(panel);
// Change the panel window owner.
NSWindow* panel_window = panel->GetNativeWindow();
[another_stack->window_ removeChildWindow:panel_window];
[window_ addChildWindow:panel_window ordered:NSWindowAbove];
}
another_stack->panels_.clear();
UpdateStackWindowBounds();
}
bool PanelStackWindowCocoa::IsEmpty() const {
return panels_.empty();
}
bool PanelStackWindowCocoa::HasPanel(Panel* panel) const {
return std::find(panels_.begin(), panels_.end(), panel) != panels_.end();
}
void PanelStackWindowCocoa::MovePanelsBy(const gfx::Vector2d& delta) {
// Moving the background stack window will cause all foreground panels window
// being moved simulatenously.
gfx::Rect enclosing_bounds = GetStackWindowBounds();
enclosing_bounds.Offset(delta);
NSRect frame = cocoa_utils::ConvertRectToCocoaCoordinates(enclosing_bounds);
[window_ setFrame:frame display:NO];
// We also need to update the panel bounds.
for (Panels::const_iterator iter = panels_.begin();
iter != panels_.end(); ++iter) {
Panel* panel = *iter;
gfx::Rect bounds = panel->GetBounds();
bounds.Offset(delta);
panel->SetPanelBoundsInstantly(bounds);
}
}
void PanelStackWindowCocoa::BeginBatchUpdatePanelBounds(bool animate) {
// If the batch animation is still in progress, continue the animation
// with the new target bounds even we want to update the bounds instantly
// this time.
if (!bounds_updates_started_) {
animate_bounds_updates_ = animate;
bounds_updates_started_ = true;
}
}
void PanelStackWindowCocoa::AddPanelBoundsForBatchUpdate(
Panel* panel, const gfx::Rect& new_bounds) {
DCHECK(bounds_updates_started_);
// No need to track it if no change is needed.
if (panel->GetBounds() == new_bounds)
return;
// Old bounds are stored as the map value.
bounds_updates_[panel] = panel->GetBounds();
// New bounds are directly applied to the value stored in native panel
// window.
static_cast<PanelCocoa*>(panel->native_panel())->set_cached_bounds_directly(
new_bounds);
}
void PanelStackWindowCocoa::EndBatchUpdatePanelBounds() {
DCHECK(bounds_updates_started_);
// No need to proceed with the animation when the bounds update list is
// empty or animation was not requested.
if (bounds_updates_.empty() || !animate_bounds_updates_) {
// Set the bounds directly when the update list is not empty.
if (!bounds_updates_.empty()) {
for (BoundsUpdates::const_iterator iter = bounds_updates_.begin();
iter != bounds_updates_.end(); ++iter) {
Panel* panel = iter->first;
NSRect frame =
cocoa_utils::ConvertRectToCocoaCoordinates(panel->GetBounds());
[panel->GetNativeWindow() setFrame:frame display:YES animate:NO];
}
bounds_updates_.clear();
UpdateStackWindowBounds();
}
bounds_updates_started_ = false;
delegate_->PanelBoundsBatchUpdateCompleted();
return;
}
// Terminate previous animation, if it is still playing.
TerminateBoundsAnimation();
// Find out if we need the animation for each panel. If the batch updates
// consist of only moving all panels by delta offset, moving the background
// window would be enough.
// If all the panels move and don't resize, just animate the underlying
// parent window. Otherwise, animate each individual panel.
bool need_to_animate_individual_panels = false;
if (bounds_updates_.size() == panels_.size()) {
gfx::Vector2d delta;
for (BoundsUpdates::const_iterator iter = bounds_updates_.begin();
iter != bounds_updates_.end(); ++iter) {
gfx::Rect old_bounds = iter->second;
gfx::Rect new_bounds = iter->first->GetBounds();
// Size should not be changed.
if (old_bounds.width() != new_bounds.width() ||
old_bounds.height() != new_bounds.height()) {
need_to_animate_individual_panels = true;
break;
}
// Origin offset should be same.
if (iter == bounds_updates_.begin()) {
delta = new_bounds.origin() - old_bounds.origin();
} else if (!(delta == new_bounds.origin() - old_bounds.origin())) {
need_to_animate_individual_panels = true;
break;
}
}
} else {
need_to_animate_individual_panels = true;
}
int num_of_animations = 1;
if (need_to_animate_individual_panels)
num_of_animations += bounds_updates_.size();
base::scoped_nsobject<NSMutableArray> animations(
[[NSMutableArray alloc] initWithCapacity:num_of_animations]);
// Add the animation for each panel in the update list.
if (need_to_animate_individual_panels) {
for (BoundsUpdates::const_iterator iter = bounds_updates_.begin();
iter != bounds_updates_.end(); ++iter) {
Panel* panel = iter->first;
NSRect panel_frame =
cocoa_utils::ConvertRectToCocoaCoordinates(panel->GetBounds());
NSDictionary* animation = [NSDictionary dictionaryWithObjectsAndKeys:
panel->GetNativeWindow(), NSViewAnimationTargetKey,
[NSValue valueWithRect:panel_frame], NSViewAnimationEndFrameKey,
nil];
[animations addObject:animation];
}
}
// Compute the final bounds that enclose all panels after the animation.
gfx::Rect enclosing_bounds;
for (Panels::const_iterator iter = panels_.begin();
iter != panels_.end(); ++iter) {
gfx::Rect target_bounds = (*iter)->GetBounds();
enclosing_bounds = UnionRects(enclosing_bounds, target_bounds);
}
// Add the animation for the background stack window.
NSRect enclosing_frame =
cocoa_utils::ConvertRectToCocoaCoordinates(enclosing_bounds);
NSDictionary* stack_animation = [NSDictionary dictionaryWithObjectsAndKeys:
window_.get(), NSViewAnimationTargetKey,
[NSValue valueWithRect:enclosing_frame], NSViewAnimationEndFrameKey,
nil];
[animations addObject:stack_animation];
// Start all the animations.
// |bounds_animation_| is released when the animation ends.
bounds_animation_ =
[[NSViewAnimation alloc] initWithViewAnimations:animations];
[bounds_animation_ setDelegate:bounds_animation_delegate_.get()];
[bounds_animation_ setDuration:PanelManager::AdjustTimeInterval(0.18)];
[bounds_animation_ setFrameRate:0.0];
[bounds_animation_ setAnimationBlockingMode: NSAnimationNonblocking];
[bounds_animation_ startAnimation];
}
bool PanelStackWindowCocoa::IsAnimatingPanelBounds() const {
return bounds_updates_started_ && animate_bounds_updates_;
}
void PanelStackWindowCocoa::BoundsUpdateAnimationEnded() {
bounds_updates_started_ = false;
for (BoundsUpdates::const_iterator iter = bounds_updates_.begin();
iter != bounds_updates_.end(); ++iter) {
Panel* panel = iter->first;
panel->manager()->OnPanelAnimationEnded(panel);
}
bounds_updates_.clear();
delegate_->PanelBoundsBatchUpdateCompleted();
}
void PanelStackWindowCocoa::Minimize() {
// Provide the custom miniwindow image since there is nothing painted for
// the background stack window.
gfx::Size stack_window_size = GetStackWindowBounds().size();
gfx::Canvas canvas(stack_window_size, 1.0f, true);
int y = 0;
Panels::const_iterator iter = panels_.begin();
for (; iter != panels_.end(); ++iter) {
Panel* panel = *iter;
gfx::Rect snapshot_bounds = gfx::Rect(panel->GetBounds().size());
std::vector<unsigned char> png;
if (!ui::GrabWindowSnapshot(panel->GetNativeWindow(),
&png,
snapshot_bounds))
break;
gfx::Image snapshot_image = gfx::Image::CreateFrom1xPNGBytes(
&(png[0]), png.size());
canvas.DrawImageInt(snapshot_image.AsImageSkia(), 0, y);
y += snapshot_bounds.height();
}
if (iter == panels_.end()) {
gfx::Image image(gfx::ImageSkia(canvas.ExtractImageRep()));
[window_ setMiniwindowImage:image.AsNSImage()];
}
[window_ miniaturize:nil];
}
bool PanelStackWindowCocoa::IsMinimized() const {
return [window_ isMiniaturized];
}
void PanelStackWindowCocoa::DrawSystemAttention(bool draw_attention) {
BOOL is_drawing_attention = attention_request_id_ != 0;
if (draw_attention == is_drawing_attention)
return;
if (draw_attention) {
attention_request_id_ = [NSApp requestUserAttention:NSCriticalRequest];
} else {
[NSApp cancelUserAttentionRequest:attention_request_id_];
attention_request_id_ = 0;
}
}
void PanelStackWindowCocoa::OnPanelActivated(Panel* panel) {
// Nothing to do.
}
void PanelStackWindowCocoa::TerminateBoundsAnimation() {
if (!bounds_animation_)
return;
[bounds_animation_ stopAnimation];
[bounds_animation_ setDelegate:nil];
[bounds_animation_ release];
bounds_animation_ = nil;
}
gfx::Rect PanelStackWindowCocoa::GetStackWindowBounds() const {
gfx::Rect enclosing_bounds;
for (Panels::const_iterator iter = panels_.begin();
iter != panels_.end(); ++iter) {
Panel* panel = *iter;
enclosing_bounds = UnionRects(enclosing_bounds, panel->GetBounds());
}
return enclosing_bounds;
}
void PanelStackWindowCocoa::UpdateStackWindowBounds() {
NSRect enclosing_bounds =
cocoa_utils::ConvertRectToCocoaCoordinates(GetStackWindowBounds());
[window_ setFrame:enclosing_bounds display:NO];
}
void PanelStackWindowCocoa::EnsureWindowCreated() {
if (window_)
return;
window_.reset(
[[NSWindow alloc] initWithContentRect:ui::kWindowSizeDeterminedLater
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO]);
[window_ setBackgroundColor:[NSColor clearColor]];
[window_ setHasShadow:YES];
[window_ setLevel:NSNormalWindowLevel];
[window_ orderFront:nil];
[window_ setTitle:base::SysUTF16ToNSString(delegate_->GetTitle())];
}
|