blob: e42c80e4044df6eced0d7d03f9a7214519a2c66a [file] [log] [blame]
// Copyright 2015 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 "components/exo/shell_surface.h"
#include "ash/aura/wm_window_aura.h"
#include "ash/common/shell_window_ids.h"
#include "ash/common/wm/window_resizer.h"
#include "ash/common/wm/window_state.h"
#include "ash/shell.h"
#include "ash/wm/window_state_aura.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_event_argument.h"
#include "components/exo/surface.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_property.h"
#include "ui/aura/window_targeter.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/hit_test.h"
#include "ui/gfx/path.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/window_util.h"
#include "ui/wm/public/activation_client.h"
DECLARE_WINDOW_PROPERTY_TYPE(std::string*)
namespace exo {
namespace {
class CustomFrameView : public views::NonClientFrameView {
public:
explicit CustomFrameView(views::Widget* widget) : widget_(widget) {}
~CustomFrameView() override {}
// Overridden from views::NonClientFrameView:
gfx::Rect GetBoundsForClientView() const override { return bounds(); }
gfx::Rect GetWindowBoundsForClientBounds(
const gfx::Rect& client_bounds) const override {
return client_bounds;
}
int NonClientHitTest(const gfx::Point& point) override {
return widget_->client_view()->NonClientHitTest(point);
}
void GetWindowMask(const gfx::Size& size, gfx::Path* window_mask) override {}
void ResetWindowControls() override {}
void UpdateWindowIcon() override {}
void UpdateWindowTitle() override {}
void SizeConstraintsChanged() override {}
private:
views::Widget* const widget_;
DISALLOW_COPY_AND_ASSIGN(CustomFrameView);
};
class CustomWindowTargeter : public aura::WindowTargeter {
public:
CustomWindowTargeter() {}
~CustomWindowTargeter() override {}
// Overridden from aura::WindowTargeter:
bool EventLocationInsideBounds(aura::Window* window,
const ui::LocatedEvent& event) const override {
Surface* surface = ShellSurface::GetMainSurface(window);
if (!surface)
return false;
gfx::Point local_point = event.location();
if (window->parent())
aura::Window::ConvertPointToTarget(window->parent(), window,
&local_point);
aura::Window::ConvertPointToTarget(window, surface, &local_point);
return surface->HitTestRect(gfx::Rect(local_point, gfx::Size(1, 1)));
}
private:
DISALLOW_COPY_AND_ASSIGN(CustomWindowTargeter);
};
class ShellSurfaceWidget : public views::Widget {
public:
explicit ShellSurfaceWidget(ShellSurface* shell_surface)
: shell_surface_(shell_surface) {}
// Overridden from views::Widget
void Close() override { shell_surface_->Close(); }
void OnKeyEvent(ui::KeyEvent* event) override {
// Handle only accelerators. Do not call Widget::OnKeyEvent that eats focus
// management keys (like the tab key) as well.
if (GetFocusManager()->ProcessAccelerator(ui::Accelerator(*event)))
event->StopPropagation();
}
private:
ShellSurface* const shell_surface_;
DISALLOW_COPY_AND_ASSIGN(ShellSurfaceWidget);
};
} // namespace
// Helper class used to coalesce a number of changes into one "configure"
// callback. Callbacks are suppressed while an instance of this class is
// instantiated and instead called when the instance is destroyed.
// If |force_configure_| is true ShellSurface::Configure() will be called
// even if no changes to shell surface took place during the lifetime of the
// ScopedConfigure instance.
class ShellSurface::ScopedConfigure {
public:
ScopedConfigure(ShellSurface* shell_surface, bool force_configure);
~ScopedConfigure();
void set_needs_configure() { needs_configure_ = true; }
private:
ShellSurface* const shell_surface_;
const bool force_configure_;
bool needs_configure_;
DISALLOW_COPY_AND_ASSIGN(ScopedConfigure);
};
// Helper class used to temporarily disable animations. Restores the
// animations disabled property when instance is destroyed.
class ShellSurface::ScopedAnimationsDisabled {
public:
explicit ScopedAnimationsDisabled(ShellSurface* shell_surface);
~ScopedAnimationsDisabled();
private:
ShellSurface* const shell_surface_;
bool saved_animations_disabled_;
DISALLOW_COPY_AND_ASSIGN(ScopedAnimationsDisabled);
};
////////////////////////////////////////////////////////////////////////////////
// ShellSurface, ScopedConfigure:
ShellSurface::ScopedConfigure::ScopedConfigure(ShellSurface* shell_surface,
bool force_configure)
: shell_surface_(shell_surface),
force_configure_(force_configure),
needs_configure_(false) {
// ScopedConfigure instances cannot be nested.
DCHECK(!shell_surface_->scoped_configure_);
shell_surface_->scoped_configure_ = this;
}
ShellSurface::ScopedConfigure::~ScopedConfigure() {
DCHECK_EQ(shell_surface_->scoped_configure_, this);
shell_surface_->scoped_configure_ = nullptr;
if (needs_configure_ || force_configure_)
shell_surface_->Configure();
// ScopedConfigure instance might have suppressed a widget bounds update.
if (shell_surface_->widget_)
shell_surface_->UpdateWidgetBounds();
}
////////////////////////////////////////////////////////////////////////////////
// ShellSurface, ScopedAnimationsDisabled:
ShellSurface::ScopedAnimationsDisabled::ScopedAnimationsDisabled(
ShellSurface* shell_surface)
: shell_surface_(shell_surface) {
if (shell_surface_->widget_) {
aura::Window* window = shell_surface_->widget_->GetNativeWindow();
saved_animations_disabled_ =
window->GetProperty(aura::client::kAnimationsDisabledKey);
window->SetProperty(aura::client::kAnimationsDisabledKey, true);
}
}
ShellSurface::ScopedAnimationsDisabled::~ScopedAnimationsDisabled() {
if (shell_surface_->widget_) {
aura::Window* window = shell_surface_->widget_->GetNativeWindow();
DCHECK_EQ(window->GetProperty(aura::client::kAnimationsDisabledKey), true);
window->SetProperty(aura::client::kAnimationsDisabledKey,
saved_animations_disabled_);
}
}
////////////////////////////////////////////////////////////////////////////////
// ShellSurface, public:
DEFINE_LOCAL_WINDOW_PROPERTY_KEY(std::string*, kApplicationIdKey, nullptr)
DEFINE_LOCAL_WINDOW_PROPERTY_KEY(Surface*, kMainSurfaceKey, nullptr)
ShellSurface::ShellSurface(Surface* surface,
ShellSurface* parent,
const gfx::Rect& initial_bounds,
bool activatable,
int container)
: widget_(nullptr),
surface_(surface),
parent_(parent ? parent->GetWidget()->GetNativeWindow() : nullptr),
initial_bounds_(initial_bounds),
activatable_(activatable),
container_(container),
pending_show_widget_(false),
scale_(1.0),
pending_scale_(1.0),
scoped_configure_(nullptr),
ignore_window_bounds_changes_(false),
resize_component_(HTCAPTION),
pending_resize_component_(HTCAPTION) {
ash::Shell::GetInstance()->activation_client()->AddObserver(this);
surface_->SetSurfaceDelegate(this);
surface_->AddSurfaceObserver(this);
surface_->Show();
set_owned_by_client();
if (parent_)
parent_->AddObserver(this);
}
ShellSurface::ShellSurface(Surface* surface)
: ShellSurface(surface,
nullptr,
gfx::Rect(),
true,
ash::kShellWindowId_DefaultContainer) {}
ShellSurface::~ShellSurface() {
DCHECK(!scoped_configure_);
ash::Shell::GetInstance()->activation_client()->RemoveObserver(this);
if (surface_) {
if (scale_ != 1.0)
surface_->SetTransform(gfx::Transform());
surface_->SetSurfaceDelegate(nullptr);
surface_->RemoveSurfaceObserver(this);
}
if (parent_)
parent_->RemoveObserver(this);
if (resizer_)
EndDrag(false /* revert */);
if (widget_) {
ash::wm::GetWindowState(widget_->GetNativeWindow())->RemoveObserver(this);
widget_->GetNativeWindow()->RemoveObserver(this);
if (widget_->IsVisible())
widget_->Hide();
widget_->CloseNow();
}
}
void ShellSurface::AcknowledgeConfigure(uint32_t serial) {
TRACE_EVENT1("exo", "ShellSurface::AcknowledgeConfigure", "serial", serial);
// Apply all configs that are older or equal to |serial|. The result is that
// the origin of the main surface will move and the resize direction will
// change to reflect the acknowledgement of configure request with |serial|
// at the next call to Commit().
while (!pending_configs_.empty()) {
auto config = pending_configs_.front();
pending_configs_.pop_front();
// Add the config offset to the accumulated offset that will be applied when
// Commit() is called.
pending_origin_offset_ += config.origin_offset;
// Set the resize direction that will be applied when Commit() is called.
pending_resize_component_ = config.resize_component;
if (config.serial == serial)
break;
}
if (widget_)
UpdateWidgetBounds();
}
void ShellSurface::SetParent(ShellSurface* parent) {
TRACE_EVENT1("exo", "ShellSurface::SetParent", "parent",
parent ? base::UTF16ToASCII(parent->title_) : "null");
if (parent_) {
parent_->RemoveObserver(this);
if (widget_)
wm::RemoveTransientChild(parent_, widget_->GetNativeWindow());
}
parent_ = parent ? parent->GetWidget()->GetNativeWindow() : nullptr;
if (parent_) {
parent_->AddObserver(this);
if (widget_)
wm::AddTransientChild(parent_, widget_->GetNativeWindow());
}
}
void ShellSurface::Maximize() {
TRACE_EVENT0("exo", "ShellSurface::Maximize");
if (!widget_)
CreateShellSurfaceWidget(ui::SHOW_STATE_MAXIMIZED);
// Note: This will ask client to configure its surface even if already
// maximized.
ScopedConfigure scoped_configure(this, true);
widget_->Maximize();
}
void ShellSurface::Minimize() {
TRACE_EVENT0("exo", "ShellSurface::Minimize");
if (!widget_)
return;
// Note: This will ask client to configure its surface even if already
// minimized.
ScopedConfigure scoped_configure(this, true);
widget_->Minimize();
}
void ShellSurface::Restore() {
TRACE_EVENT0("exo", "ShellSurface::Restore");
if (!widget_)
return;
// Note: This will ask client to configure its surface even if not already
// maximized or minimized.
ScopedConfigure scoped_configure(this, true);
widget_->Restore();
}
void ShellSurface::SetFullscreen(bool fullscreen) {
TRACE_EVENT1("exo", "ShellSurface::SetFullscreen", "fullscreen", fullscreen);
if (!widget_)
CreateShellSurfaceWidget(ui::SHOW_STATE_FULLSCREEN);
// Note: This will ask client to configure its surface even if fullscreen
// state doesn't change.
ScopedConfigure scoped_configure(this, true);
widget_->SetFullscreen(fullscreen);
}
void ShellSurface::SetTitle(const base::string16& title) {
TRACE_EVENT1("exo", "ShellSurface::SetTitle", "title",
base::UTF16ToUTF8(title));
title_ = title;
if (widget_)
widget_->UpdateWindowTitle();
}
// static
void ShellSurface::SetApplicationId(aura::Window* window,
std::string* application_id) {
window->SetProperty(kApplicationIdKey, application_id);
}
// static
const std::string ShellSurface::GetApplicationId(aura::Window* window) {
std::string* string_ptr = window->GetProperty(kApplicationIdKey);
return string_ptr ? *string_ptr : std::string();
}
void ShellSurface::SetApplicationId(const std::string& application_id) {
TRACE_EVENT1("exo", "ShellSurface::SetApplicationId", "application_id",
application_id);
application_id_ = application_id;
}
void ShellSurface::Move() {
TRACE_EVENT0("exo", "ShellSurface::Move");
if (widget_ && !widget_->movement_disabled())
AttemptToStartDrag(HTCAPTION);
}
void ShellSurface::Resize(int component) {
TRACE_EVENT1("exo", "ShellSurface::Resize", "component", component);
if (widget_ && !widget_->movement_disabled())
AttemptToStartDrag(component);
}
void ShellSurface::Close() {
if (!close_callback_.is_null())
close_callback_.Run();
}
void ShellSurface::SetGeometry(const gfx::Rect& geometry) {
TRACE_EVENT1("exo", "ShellSurface::SetGeometry", "geometry",
geometry.ToString());
if (geometry.IsEmpty()) {
DLOG(WARNING) << "Surface geometry must be non-empty";
return;
}
pending_geometry_ = geometry;
}
void ShellSurface::SetScale(double scale) {
TRACE_EVENT1("exo", "ShellSurface::SetScale", "scale", scale);
if (scale <= 0.0) {
DLOG(WARNING) << "Surface scale must be greater than 0";
return;
}
pending_scale_ = scale;
}
// static
void ShellSurface::SetMainSurface(aura::Window* window, Surface* surface) {
window->SetProperty(kMainSurfaceKey, surface);
}
// static
Surface* ShellSurface::GetMainSurface(const aura::Window* window) {
return window->GetProperty(kMainSurfaceKey);
}
std::unique_ptr<base::trace_event::TracedValue> ShellSurface::AsTracedValue()
const {
std::unique_ptr<base::trace_event::TracedValue> value(
new base::trace_event::TracedValue());
value->SetString("title", base::UTF16ToUTF8(title_));
value->SetString("application_id", application_id_);
return value;
}
////////////////////////////////////////////////////////////////////////////////
// SurfaceDelegate overrides:
void ShellSurface::OnSurfaceCommit() {
surface_->CommitSurfaceHierarchy();
if (enabled() && !widget_)
CreateShellSurfaceWidget(ui::SHOW_STATE_NORMAL);
// Apply the accumulated pending origin offset to reflect acknowledged
// configure requests.
origin_ += pending_origin_offset_;
pending_origin_offset_ = gfx::Vector2d();
// Update resize direction to reflect acknowledged configure requests.
resize_component_ = pending_resize_component_;
if (widget_) {
// Apply new window geometry.
geometry_ = pending_geometry_;
UpdateWidgetBounds();
gfx::Point surface_origin = GetSurfaceOrigin();
gfx::Rect hit_test_bounds =
surface_->GetHitTestBounds() + surface_origin.OffsetFromOrigin();
// Prevent window from being activated when hit test bounds are empty.
bool activatable = activatable_ && !hit_test_bounds.IsEmpty();
if (activatable != CanActivate()) {
set_can_activate(activatable);
// Activate or deactivate window if activation state changed.
aura::client::ActivationClient* activation_client =
ash::Shell::GetInstance()->activation_client();
if (activatable)
activation_client->ActivateWindow(widget_->GetNativeWindow());
else if (widget_->IsActive())
activation_client->DeactivateWindow(widget_->GetNativeWindow());
}
// Update surface bounds.
surface_->SetBounds(gfx::Rect(surface_origin, surface_->layer()->size()));
// Update surface scale.
if (pending_scale_ != scale_) {
gfx::Transform transform;
DCHECK_NE(pending_scale_, 0.0);
transform.Scale(1.0 / pending_scale_, 1.0 / pending_scale_);
surface_->SetTransform(transform);
scale_ = pending_scale_;
}
// Show widget if needed.
if (pending_show_widget_) {
DCHECK(!widget_->IsClosed());
DCHECK(!widget_->IsVisible());
pending_show_widget_ = false;
widget_->Show();
}
}
}
bool ShellSurface::IsSurfaceSynchronized() const {
// A shell surface is always desynchronized.
return false;
}
////////////////////////////////////////////////////////////////////////////////
// SurfaceObserver overrides:
void ShellSurface::OnSurfaceDestroying(Surface* surface) {
if (resizer_)
EndDrag(false /* revert */);
if (widget_)
SetMainSurface(widget_->GetNativeWindow(), nullptr);
surface->RemoveSurfaceObserver(this);
surface_ = nullptr;
// Hide widget before surface is destroyed. This allows hide animations to
// run using the current surface contents.
if (widget_)
widget_->Hide();
// Note: In its use in the Wayland server implementation, the surface
// destroyed callback may destroy the ShellSurface instance. This call needs
// to be last so that the instance can be destroyed.
if (!surface_destroyed_callback_.is_null())
surface_destroyed_callback_.Run();
}
////////////////////////////////////////////////////////////////////////////////
// views::WidgetDelegate overrides:
bool ShellSurface::CanResize() const {
return initial_bounds_.IsEmpty();
}
bool ShellSurface::CanMaximize() const {
return true;
}
bool ShellSurface::CanMinimize() const {
return true;
}
base::string16 ShellSurface::GetWindowTitle() const {
return title_;
}
void ShellSurface::WindowClosing() {
if (resizer_)
EndDrag(true /* revert */);
SetEnabled(false);
widget_ = nullptr;
}
views::Widget* ShellSurface::GetWidget() {
return widget_;
}
const views::Widget* ShellSurface::GetWidget() const {
return widget_;
}
views::View* ShellSurface::GetContentsView() {
return this;
}
views::NonClientFrameView* ShellSurface::CreateNonClientFrameView(
views::Widget* widget) {
return new CustomFrameView(widget);
}
bool ShellSurface::WidgetHasHitTestMask() const {
return surface_ ? surface_->HasHitTestMask() : false;
}
void ShellSurface::GetWidgetHitTestMask(gfx::Path* mask) const {
DCHECK(WidgetHasHitTestMask());
surface_->GetHitTestMask(mask);
gfx::Point origin = surface_->bounds().origin();
mask->offset(SkIntToScalar(origin.x()), SkIntToScalar(origin.y()));
}
////////////////////////////////////////////////////////////////////////////////
// views::Views overrides:
gfx::Size ShellSurface::GetPreferredSize() const {
if (!geometry_.IsEmpty())
return geometry_.size();
return surface_ ? surface_->layer()->size() : gfx::Size();
}
////////////////////////////////////////////////////////////////////////////////
// ash::wm::WindowStateObserver overrides:
void ShellSurface::OnPreWindowStateTypeChange(
ash::wm::WindowState* window_state,
ash::wm::WindowStateType old_type) {
ash::wm::WindowStateType new_type = window_state->GetStateType();
if (ash::wm::IsMaximizedOrFullscreenWindowStateType(old_type) ||
ash::wm::IsMaximizedOrFullscreenWindowStateType(new_type)) {
// When transitioning in/out of maximized or fullscreen mode we need to
// make sure we have a configure callback before we allow the default
// cross-fade animations. The configure callback provides a mechanism for
// the client to inform us that a frame has taken the state change into
// account and without this cross-fade animations are unreliable.
if (configure_callback_.is_null())
scoped_animations_disabled_.reset(new ScopedAnimationsDisabled(this));
}
}
void ShellSurface::OnPostWindowStateTypeChange(
ash::wm::WindowState* window_state,
ash::wm::WindowStateType old_type) {
ash::wm::WindowStateType new_type = window_state->GetStateType();
if (ash::wm::IsMaximizedOrFullscreenWindowStateType(old_type) ||
ash::wm::IsMaximizedOrFullscreenWindowStateType(new_type)) {
Configure();
}
if (widget_)
UpdateWidgetBounds();
if (!state_changed_callback_.is_null())
state_changed_callback_.Run(old_type, new_type);
// Re-enable animations if they were disabled in pre state change handler.
scoped_animations_disabled_.reset();
}
////////////////////////////////////////////////////////////////////////////////
// aura::WindowObserver overrides:
void ShellSurface::OnWindowBoundsChanged(aura::Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds) {
if (!widget_ || !surface_ || ignore_window_bounds_changes_)
return;
if (window == widget_->GetNativeWindow()) {
if (new_bounds.size() == old_bounds.size())
return;
// If size changed then give the client a chance to produce new contents
// before origin on screen is changed by adding offset to the next configure
// request and offset |origin_| by the same distance.
gfx::Vector2d origin_offset = new_bounds.origin() - old_bounds.origin();
pending_origin_config_offset_ += origin_offset;
origin_ -= origin_offset;
surface_->SetBounds(
gfx::Rect(GetSurfaceOrigin(), surface_->layer()->size()));
Configure();
}
}
void ShellSurface::OnWindowDestroying(aura::Window* window) {
if (window == parent_) {
parent_ = nullptr;
// Disable shell surface in case parent is destroyed before shell surface
// widget has been created.
SetEnabled(false);
}
window->RemoveObserver(this);
}
////////////////////////////////////////////////////////////////////////////////
// aura::client::ActivationChangeObserver overrides:
void ShellSurface::OnWindowActivated(
aura::client::ActivationChangeObserver::ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) {
if (!widget_)
return;
if (gained_active == widget_->GetNativeWindow() ||
lost_active == widget_->GetNativeWindow()) {
DCHECK(activatable_);
Configure();
}
}
////////////////////////////////////////////////////////////////////////////////
// ui::EventHandler overrides:
void ShellSurface::OnKeyEvent(ui::KeyEvent* event) {
if (!resizer_) {
views::View::OnKeyEvent(event);
return;
}
if (event->type() == ui::ET_KEY_PRESSED &&
event->key_code() == ui::VKEY_ESCAPE) {
EndDrag(true /* revert */);
}
}
void ShellSurface::OnMouseEvent(ui::MouseEvent* event) {
if (!resizer_) {
views::View::OnMouseEvent(event);
return;
}
if (event->handled())
return;
if ((event->flags() &
(ui::EF_MIDDLE_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON)) != 0)
return;
if (event->type() == ui::ET_MOUSE_CAPTURE_CHANGED) {
// We complete the drag instead of reverting it, as reverting it will
// result in a weird behavior when a client produces a modal dialog
// while the drag is in progress.
EndDrag(false /* revert */);
return;
}
switch (event->type()) {
case ui::ET_MOUSE_DRAGGED: {
gfx::Point location(event->location());
aura::Window::ConvertPointToTarget(widget_->GetNativeWindow(),
widget_->GetNativeWindow()->parent(),
&location);
ScopedConfigure scoped_configure(this, false);
resizer_->Drag(location, event->flags());
event->StopPropagation();
break;
}
case ui::ET_MOUSE_RELEASED: {
ScopedConfigure scoped_configure(this, false);
EndDrag(false /* revert */);
break;
}
case ui::ET_MOUSE_MOVED:
case ui::ET_MOUSE_PRESSED:
case ui::ET_MOUSE_ENTERED:
case ui::ET_MOUSE_EXITED:
case ui::ET_MOUSEWHEEL:
case ui::ET_MOUSE_CAPTURE_CHANGED:
break;
default:
NOTREACHED();
break;
}
}
////////////////////////////////////////////////////////////////////////////////
// ShellSurface, private:
void ShellSurface::CreateShellSurfaceWidget(ui::WindowShowState show_state) {
DCHECK(enabled());
DCHECK(!widget_);
views::Widget::InitParams params;
params.type = views::Widget::InitParams::TYPE_WINDOW;
params.ownership = views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET;
params.delegate = this;
params.shadow_type = views::Widget::InitParams::SHADOW_TYPE_NONE;
params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
params.show_state = show_state;
params.parent =
ash::Shell::GetContainer(ash::Shell::GetPrimaryRootWindow(), container_);
params.bounds = initial_bounds_;
bool activatable = activatable_ && !surface_->GetHitTestBounds().IsEmpty();
params.activatable = activatable ? views::Widget::InitParams::ACTIVATABLE_YES
: views::Widget::InitParams::ACTIVATABLE_NO;
// Note: NativeWidget owns this widget.
widget_ = new ShellSurfaceWidget(this);
widget_->Init(params);
// Disable movement if initial bounds were specified.
widget_->set_movement_disabled(!initial_bounds_.IsEmpty());
aura::Window* window = widget_->GetNativeWindow();
window->SetName("ExoShellSurface");
window->AddChild(surface_);
window->SetEventTargeter(base::WrapUnique(new CustomWindowTargeter));
SetApplicationId(window, &application_id_);
SetMainSurface(window, surface_);
// Start tracking changes to window bounds and window state.
window->AddObserver(this);
ash::wm::GetWindowState(window)->AddObserver(this);
// Make shell surface a transient child if |parent_| has been set.
if (parent_)
wm::AddTransientChild(parent_, window);
// Allow Ash to manage the position of a top-level shell surfaces if show
// state is one that allows auto positioning and |initial_bounds_| has
// not been set.
ash::wm::GetWindowState(window)->set_window_position_managed(
ash::wm::ToWindowShowState(ash::wm::WINDOW_STATE_TYPE_AUTO_POSITIONED) ==
show_state &&
initial_bounds_.IsEmpty());
// Show widget next time Commit() is called.
pending_show_widget_ = true;
}
void ShellSurface::Configure() {
DCHECK(widget_);
// Delay configure callback if |scoped_configure_| is set.
if (scoped_configure_) {
scoped_configure_->set_needs_configure();
return;
}
gfx::Vector2d origin_offset = pending_origin_config_offset_;
pending_origin_config_offset_ = gfx::Vector2d();
// If surface is being resized, save the resize direction.
int resize_component =
resizer_ ? resizer_->details().window_component : HTCAPTION;
if (configure_callback_.is_null()) {
pending_origin_offset_ += origin_offset;
pending_resize_component_ = resize_component;
return;
}
uint32_t serial = configure_callback_.Run(
widget_->GetWindowBoundsInScreen().size(),
ash::wm::GetWindowState(widget_->GetNativeWindow())->GetStateType(),
IsResizing(), widget_->IsActive());
// Apply origin offset and resize component at the first Commit() after this
// configure request has been acknowledged.
pending_configs_.push_back({serial, origin_offset, resize_component});
LOG_IF(WARNING, pending_configs_.size() > 100)
<< "Number of pending configure acks for shell surface has reached: "
<< pending_configs_.size();
}
void ShellSurface::AttemptToStartDrag(int component) {
DCHECK(widget_);
// Cannot start another drag if one is already taking place.
if (resizer_)
return;
if (widget_->GetNativeWindow()->HasCapture())
return;
aura::Window* root_window = widget_->GetNativeWindow()->GetRootWindow();
gfx::Point drag_location =
root_window->GetHost()->dispatcher()->GetLastMouseLocationInRoot();
aura::Window::ConvertPointToTarget(
root_window, widget_->GetNativeWindow()->parent(), &drag_location);
// Set the cursor before calling CreateWindowResizer(), as that will
// eventually call LockCursor() and prevent the cursor from changing.
aura::client::CursorClient* cursor_client =
aura::client::GetCursorClient(root_window);
DCHECK(cursor_client);
switch (component) {
case HTCAPTION:
cursor_client->SetCursor(ui::kCursorPointer);
break;
case HTTOP:
cursor_client->SetCursor(ui::kCursorNorthResize);
break;
case HTTOPRIGHT:
cursor_client->SetCursor(ui::kCursorNorthEastResize);
break;
case HTRIGHT:
cursor_client->SetCursor(ui::kCursorEastResize);
break;
case HTBOTTOMRIGHT:
cursor_client->SetCursor(ui::kCursorSouthEastResize);
break;
case HTBOTTOM:
cursor_client->SetCursor(ui::kCursorSouthResize);
break;
case HTBOTTOMLEFT:
cursor_client->SetCursor(ui::kCursorSouthWestResize);
break;
case HTLEFT:
cursor_client->SetCursor(ui::kCursorWestResize);
break;
case HTTOPLEFT:
cursor_client->SetCursor(ui::kCursorNorthWestResize);
break;
default:
NOTREACHED();
break;
}
resizer_ = ash::CreateWindowResizer(
ash::WmWindowAura::Get(widget_->GetNativeWindow()), drag_location,
component, aura::client::WINDOW_MOVE_SOURCE_MOUSE);
if (!resizer_)
return;
// Apply pending origin offsets and resize direction before starting a new
// resize operation. These can still be pending if the client has acknowledged
// the configure request but not yet called Commit().
origin_ += pending_origin_offset_;
pending_origin_offset_ = gfx::Vector2d();
resize_component_ = pending_resize_component_;
ash::Shell::GetInstance()->AddPreTargetHandler(this);
widget_->GetNativeWindow()->SetCapture();
// Notify client that resizing state has changed.
if (IsResizing())
Configure();
}
void ShellSurface::EndDrag(bool revert) {
DCHECK(widget_);
DCHECK(resizer_);
bool was_resizing = IsResizing();
if (revert)
resizer_->RevertDrag();
else
resizer_->CompleteDrag();
ash::Shell::GetInstance()->RemovePreTargetHandler(this);
widget_->GetNativeWindow()->ReleaseCapture();
resizer_.reset();
// Notify client that resizing state has changed.
if (was_resizing)
Configure();
UpdateWidgetBounds();
}
bool ShellSurface::IsResizing() const {
if (!resizer_)
return false;
return resizer_->details().bounds_change &
ash::WindowResizer::kBoundsChange_Resizes;
}
gfx::Rect ShellSurface::GetVisibleBounds() const {
// Use |geometry_| if set, otherwise use the visual bounds of the surface.
return geometry_.IsEmpty() ? gfx::Rect(surface_->layer()->size()) : geometry_;
}
gfx::Point ShellSurface::GetSurfaceOrigin() const {
gfx::Rect window_bounds = widget_->GetNativeWindow()->bounds();
// If initial bounds were specified then surface origin is always relative
// to those bounds.
if (!initial_bounds_.IsEmpty())
return initial_bounds_.origin() - window_bounds.OffsetFromOrigin();
gfx::Rect visible_bounds = GetVisibleBounds();
switch (resize_component_) {
case HTCAPTION:
return origin_ - visible_bounds.OffsetFromOrigin();
case HTBOTTOM:
case HTRIGHT:
case HTBOTTOMRIGHT:
return gfx::Point() - visible_bounds.OffsetFromOrigin();
case HTTOP:
case HTTOPRIGHT:
return gfx::Point(0, window_bounds.height() - visible_bounds.height()) -
visible_bounds.OffsetFromOrigin();
break;
case HTLEFT:
case HTBOTTOMLEFT:
return gfx::Point(window_bounds.width() - visible_bounds.width(), 0) -
visible_bounds.OffsetFromOrigin();
case HTTOPLEFT:
return gfx::Point(window_bounds.width() - visible_bounds.width(),
window_bounds.height() - visible_bounds.height()) -
visible_bounds.OffsetFromOrigin();
default:
NOTREACHED();
return gfx::Point();
}
}
void ShellSurface::UpdateWidgetBounds() {
DCHECK(widget_);
// Return early if the shell is currently managing the bounds of the widget.
if (widget_->IsMaximized() || widget_->IsFullscreen() || IsResizing())
return;
// Return early if there is pending configure requests.
if (!pending_configs_.empty() || scoped_configure_)
return;
gfx::Rect visible_bounds = GetVisibleBounds();
gfx::Rect new_widget_bounds = visible_bounds;
// Avoid changing widget origin unless initial bounds were specificed and
// widget origin is always relative to it.
if (initial_bounds_.IsEmpty())
new_widget_bounds.set_origin(widget_->GetNativeWindow()->bounds().origin());
// Update widget origin using the surface origin if the current location of
// surface is being anchored to one side of the widget as a result of a
// resize operation.
if (resize_component_ != HTCAPTION) {
gfx::Point new_widget_origin =
GetSurfaceOrigin() + visible_bounds.OffsetFromOrigin();
aura::Window::ConvertPointToTarget(widget_->GetNativeWindow(),
widget_->GetNativeWindow()->parent(),
&new_widget_origin);
new_widget_bounds.set_origin(new_widget_origin);
}
// Set |ignore_window_bounds_changes_| as this change to window bounds
// should not result in a configure request.
DCHECK(!ignore_window_bounds_changes_);
ignore_window_bounds_changes_ = true;
if (widget_->GetNativeWindow()->bounds() != new_widget_bounds)
widget_->SetBounds(new_widget_bounds);
ignore_window_bounds_changes_ = false;
// A change to the widget size requires surface bounds to be re-adjusted.
surface_->SetBounds(gfx::Rect(GetSurfaceOrigin(), surface_->layer()->size()));
}
} // namespace exo