blob: 544eb5a6f66e9058c273285e303a25f675d5f37e [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/shell.h"
#include "ash/shell_window_ids.h"
#include "ash/wm/window_state.h"
#include "base/logging.h"
#include "base/macros.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/window.h"
#include "ui/aura/window_property.h"
#include "ui/aura/window_targeter.h"
#include "ui/base/hit_test.h"
#include "ui/gfx/path.h"
#include "ui/views/widget/widget.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 {
return PointInsideBounds(window, event.location());
}
private:
bool PointInsideBounds(const aura::Window* window,
const gfx::Point& point) const {
Surface* surface = ShellSurface::GetMainSurface(window);
if (!surface)
return false;
gfx::Point local_point = point;
if (window->parent()) {
aura::Window::ConvertPointToTarget(window->parent(), window,
&local_point);
}
// If point is inside a child window then it's also inside the parent.
for (const aura::Window* child : window->children()) {
if (PointInsideBounds(child, local_point))
return true;
}
aura::Window::ConvertPointToTarget(window, surface, &local_point);
return surface->HitTestRect(gfx::Rect(local_point, gfx::Size(1, 1)));
}
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(); }
private:
ShellSurface* const shell_surface_;
DISALLOW_COPY_AND_ASSIGN(ShellSurfaceWidget);
};
} // namespace
////////////////////////////////////////////////////////////////////////////////
// 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)
: surface_(surface),
parent_(parent ? parent->GetWidget()->GetNativeWindow() : nullptr),
initial_bounds_(initial_bounds) {
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()) {}
ShellSurface::~ShellSurface() {
ash::Shell::GetInstance()->activation_client()->RemoveObserver(this);
if (surface_) {
surface_->SetSurfaceDelegate(nullptr);
surface_->RemoveSurfaceObserver(this);
}
if (parent_)
parent_->RemoveObserver(this);
if (widget_) {
ash::wm::GetWindowState(widget_->GetNativeWindow())->RemoveObserver(this);
if (widget_->IsVisible())
widget_->Hide();
widget_->CloseNow();
}
}
void ShellSurface::SetParent(ShellSurface* parent) {
TRACE_EVENT1("exo", "ShellSurface::SetParent", "parent",
parent ? base::UTF16ToASCII(parent->title_) : "null");
if (parent_)
parent_->RemoveObserver(this);
parent_ = parent ? parent->GetWidget()->GetNativeWindow() : nullptr;
if (parent_)
parent_->AddObserver(this);
}
void ShellSurface::Maximize() {
TRACE_EVENT0("exo", "ShellSurface::Maximize");
if (!widget_)
CreateShellSurfaceWidget();
// Ask client to configure its surface if already maximized.
if (widget_->IsMaximized()) {
Configure();
return;
}
widget_->Maximize();
}
void ShellSurface::Restore() {
TRACE_EVENT0("exo", "ShellSurface::Restore");
if (!widget_)
return;
// Ask client to configure its surface if already restored.
if (!widget_->IsMaximized()) {
Configure();
return;
}
widget_->Restore();
}
void ShellSurface::SetFullscreen(bool fullscreen) {
TRACE_EVENT1("exo", "ShellSurface::SetFullscreen", "fullscreen", fullscreen);
if (!widget_)
CreateShellSurfaceWidget();
// Ask client to configure its surface if fullscreen state is not changing.
if (widget_->IsFullscreen() == fullscreen) {
Configure();
return;
}
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_->RunMoveLoop(gfx::Vector2d(), views::Widget::MOVE_LOOP_SOURCE_MOUSE,
views::Widget::MOVE_LOOP_ESCAPE_BEHAVIOR_DONT_HIDE);
}
}
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;
}
geometry_ = geometry;
}
// 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);
}
scoped_ptr<base::trace_event::TracedValue> ShellSurface::AsTracedValue() const {
scoped_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();
if (widget_) {
// Update surface bounds and widget size.
gfx::Point origin;
views::View::ConvertPointToWidget(this, &origin);
// Use |geometry_| if set, otherwise use the visual bounds of the surface.
gfx::Rect geometry =
geometry_.IsEmpty() ? surface_->GetVisibleBounds() : geometry_;
surface_->SetBounds(gfx::Rect(origin - geometry.OffsetFromOrigin(),
surface_->layer()->size()));
widget_->SetSize(geometry.size());
// Show widget if not already visible.
if (!widget_->IsClosed() && !widget_->IsVisible())
widget_->Show();
}
}
bool ShellSurface::IsSurfaceSynchronized() const {
// A shell surface is always desynchronized.
return false;
}
////////////////////////////////////////////////////////////////////////////////
// SurfaceObserver overrides:
void ShellSurface::OnSurfaceDestroying(Surface* surface) {
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:
base::string16 ShellSurface::GetWindowTitle() const {
return title_;
}
views::Widget* ShellSurface::GetWidget() {
return widget_.get();
}
const views::Widget* ShellSurface::GetWidget() const {
return widget_.get();
}
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_->GetVisibleBounds().size() : gfx::Size();
}
////////////////////////////////////////////////////////////////////////////////
// ash::wm::WindowStateObserver overrides:
void ShellSurface::OnPostWindowStateTypeChange(
ash::wm::WindowState* window_state,
ash::wm::WindowStateType old_type) {
ash::wm::WindowStateType new_type = window_state->GetStateType();
if (old_type == ash::wm::WINDOW_STATE_TYPE_MAXIMIZED ||
new_type == ash::wm::WINDOW_STATE_TYPE_MAXIMIZED ||
old_type == ash::wm::WINDOW_STATE_TYPE_FULLSCREEN ||
new_type == ash::wm::WINDOW_STATE_TYPE_FULLSCREEN) {
Configure();
}
}
////////////////////////////////////////////////////////////////////////////////
// aura::WindowObserver overrides:
void ShellSurface::OnWindowDestroying(aura::Window* window) {
window->RemoveObserver(this);
parent_ = nullptr;
// Disable shell surface in case parent is destroyed before shell surface
// widget has been created.
SetEnabled(false);
}
////////////////////////////////////////////////////////////////////////////////
// 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()) {
Configure();
}
}
////////////////////////////////////////////////////////////////////////////////
// ShellSurface, private:
void ShellSurface::CreateShellSurfaceWidget() {
DCHECK(enabled());
DCHECK(!widget_);
views::Widget::InitParams params;
params.type = views::Widget::InitParams::TYPE_WINDOW;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.delegate = this;
params.shadow_type = views::Widget::InitParams::SHADOW_TYPE_NONE;
params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
params.show_state = ui::SHOW_STATE_NORMAL;
gfx::Point position(initial_bounds_.origin());
if (parent_) {
params.child = true;
params.parent = parent_;
aura::Window::ConvertPointToTarget(GetMainSurface(parent_), parent_,
&position);
} else {
params.parent =
ash::Shell::GetContainer(ash::Shell::GetPrimaryRootWindow(),
ash::kShellWindowId_DefaultContainer);
}
params.bounds = gfx::Rect(position, initial_bounds_.size());
widget_.reset(new ShellSurfaceWidget(this));
widget_->Init(params);
widget_->GetNativeWindow()->set_owned_by_parent(false);
widget_->GetNativeWindow()->SetName("ExoShellSurface");
widget_->GetNativeWindow()->AddChild(surface_);
widget_->GetNativeWindow()->SetEventTargeter(
make_scoped_ptr(new CustomWindowTargeter));
SetApplicationId(widget_->GetNativeWindow(), &application_id_);
SetMainSurface(widget_->GetNativeWindow(), surface_);
// Start tracking window state changes.
ash::wm::GetWindowState(widget_->GetNativeWindow())->AddObserver(this);
// The position of a top-level shell surface is managed by Ash.
ash::wm::GetWindowState(widget_->GetNativeWindow())
->set_window_position_managed(true);
}
void ShellSurface::Configure() {
DCHECK(widget_);
if (configure_callback_.is_null())
return;
configure_callback_.Run(
widget_->GetWindowBoundsInScreen().size(),
ash::wm::GetWindowState(widget_->GetNativeWindow())->GetStateType(),
widget_->IsActive());
}
} // namespace exo